Comments (12)
[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:
- To select an interface, base class, or alias without performing a conversion.
- 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.
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:
- Make it call
Clone()
without boxing theFoo
instance. I'm fairly sure the CLR can handle this: theconstrained. T
opcode does just that. But this feature doesn't really need CLR support, because the compiler can just emit a direct call toFoo.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()
. - 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.
- Box the
struct
and don't issue a warning. This does get rid of the annoyance of warning the user whenFoo
is an immutable struct. In that case, whetherFoo
gets boxed or not doesn't matter in terms of run-time semantics.
Personally, I'm leaning toward the first option.
from ecsharp.
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<R>).Clone()</c> will
/// not perform boxing.
/// </remarks>
public struct RangeEnumerator<R, T> : IEnumerator<T> where R : IFRange<T>, ICloneable<R>
from ecsharp.
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.
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.
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.
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.
Great work, by the way!
from ecsharp.
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.
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.
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.
Sure.
from ecsharp.
Related Issues (20)
- Base compileTime on dotnet-script CLI tool instead of Roslyn scripting HOT 11
- Support F#ish object expression or Java#ish anonymous clases HOT 2
- Add `define` macro with first argument treated as `this` to enable chaining HOT 29
- Comments before multi-using statements are erased HOT 4
- await fluent operator HOT 1
- Error when transforming the `typeof(X<>)` construct HOT 2
- The error Semicolon': expected Colon for the specific code HOT 2
- Support C# 9 pattern matching HOT 3
- Ref locals cannot have parentheses
- EC#: Syntax error in `Foo<T?>`
- Qualified names of identifier macros not working?
- 'with' or quick binding operator bug HOT 1
- EC#: [return: ...] attribute sometimes causes IndexOutOfRangeException in InternalList
- EC#: `#pragma warning` is not propagated to output file
- EC#: Add support for suffix `!` operator
- EC#: New `?>` operator causes parser errors in code like `X<T?>` HOT 1
- Lemp integration - custom macro is not been called
- Lemp - Match only on attribute name
- Add Deconstruct Method To All LNode types to use c# pattern matching
- FPL Abs bug HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ecsharp.