Open jcouv opened 6 years ago
@jcouv What is the intended behavior for operators? My guess is that for equality, it should behave exactly the same as new T() == x
where T
is inferred from the type of the other operand (if any). unary operators would be all disallowed.
class D {}
class C
{
public static bool operator+(C s, D c);
public static bool operator+(C s, C c);
static void Main()
{
var x = new C() + new(); // error? what if we remove either or both of operators above?
}
}
default
produces an error, which I think is the most sensible output here as well.
Note: if we allow this we probably shouldn't infer string or object from string concatenation.
I agree. The behavior for default
is a good guide.
Looking at the default
proposal, I see some other interesting cases (annotated with my expectations, let me know if they make sense):
new() is T
(disallow)x is new()
(disallow since not constant)new() as RefType
(allow)throw new();
(disallow since no type and not null
literal)new() == new()
(disallow since no target type)new() == <expr>
(allow as long as <expr>
has a type)new() == (1, 2)
(kinda useless, new()
isn't allowed to take tuple typecase new():
(disallow since not constant)As a side note, I've updated the proposal for default
to list LDM meeting notes, if that helps.
Do we want to only permit equality (user-defined and built-in) among comparison and arithmetic operators?
I don't see a reason to restrict operators, as long as we have a type to target and that type is allowed for target-typed new.
This rules out unary operators (no type to target).
For types that are disallowed, it would be good to update the proposal to match LDM notes.
If I followed correctly, built-in types (such as int
, string
, ...) are allowed. Tuples are allowed too.
I don't see a reason to restrict operators
From LDM notes:
Don't allow default as an operand to a unary or binary operator. We need to protect the ability to add new operator overloads in the future.
With the exception of equality. So I'm thinking that the idea is to follow default
here.
new() == (1, 2)
looks like LDM allowed it after all
To me it's weird because new
would be redundant for both tuples and delegates (also from LDM)
(int a, int b) t = new(1, 2); // "new" is redundant
Action a = new(() => {}); // "new" is redundant
(int a, int b) t = new(); // ruled out by "use of struct default ctor"
Action a = new(); // no constructor found
var x = new() == (1, 2); // ruled out by "use of struct default ctor"
var x = new(1, 2) == (1, 2) // "new" is redundant
Can you confirm if this is the intended result?
throw new()
(disallow since no type and not null literal)
That might be actually useful (with Exception as the type)? (currently disallowed)
Thanks for the correction. Then let's restrict on operators (like we do for default
) :-)
For tuples, the LDM notes are pretty explicit (ie. allow). We could re-discuss.
My view is new(...)
isn't useful or desirable for types that have literals (int
, string
, tuples). But at the same time, it may be strange to block those.
For throw
, let's keep it as an open issue as well.
Could you make a PR to update the proposal, including those three points (operators, tuples/literals, throw) as open issues? Thanks. Next LDM will be late August.
Not sure if this is already included, but we'll need to adjust type inferrer here.
Probably there's more, because Type
is now TypeSyntax?
.
Oh. We changed the syntax to make somehting that could previous not be null into someting that can be null? That's definitely concerning. No telling how many things that may break in/out of the roslyn codebase. Any analyzers themselves may be screwed on that. Is compat council ok with that approach? I thought it was a no-no in the past...
Any reason this isn't done by introducing something like BaseObjectCreationSyntax, then leaving the existing guy alone (except moving onto that), and then having the new guy derive from that, but have no type-syntax?
That way code has to opt into handling this properly.
Thoughts?
introducing something like BaseObjectCreationSyntax, then leaving the existing guy alone (except moving onto that), and then having the new guy derive from that, but have no type-syntax?
Not sure if having a "Base" would help with any scenario (if not breaking). I think we can just follow ImplicitArrayCreation (having ImplicitObjectCreation deriving from Expression).
Either way, I agree it's a better approach than just making the field nullable, at least not until we have NRT.
But I agree it's a better approach than just making the field nullable, at least not until we have NRT.
NRT doesn't even help here. Because that presumes that existing code woudl actually be recompiled to even notice this field could now be null. There may be extensions out there that would not and would simply crash on this.
Not sure if having a "Base" would help with any scenario (if not breaking)
Introducing a 'Base' form is actually fine. This is what we did with 'foreach' statements when we added support for the deconstruction form: foreach (var (x, y) in ...
We introduced a CommonForEachStatementSyntax base node and made the existing ForEachStatementSyntax derive from it. We then introduced a new ForEachVariableStatementSyntax for the new form.
I think we can just follow ImplicitArrayCreation (having ImplicitObjectCreation deriving from Expression).
This would also be fine with me.
I'm probably late to this discussion, but why this peculiar "reverse" inference?
Dictionary<string, List<int>> foo = new();
I mean, you can see why this looks odd when you place it next to other initializations - most people would naturally expect this:
var foo = new Dictionary<string, List<int>>();
var bar = 123;
var baz = "hello";
Why are other variable types inferred from the initialization expression, while for object types instead you declare the type and infer the operand of the new-operator?
Is there a practical reason for this inconsistency?
Is there a practical reason for this inconsistency?
I'm not seeing the inconsistency. You can already do teh latter form today. This is enabling the former form.
I mean, you can see why this looks odd when you place it next to other initializations
It's unclear why you would do that. If you're using 'var' and 'specifying the type on the right' then i would expect you would continue doing that.
I'm on latest VS 2019 preview, and I've noticed auto-complete popup doesn't work for new() class initializers. As in:
var color = new Color()
{
// ctrl+space and suggestions are "r, g, b, a"
}
Color color = new()
{
// ctrl+space has no response
}
Is this covered by the test plan?
Thanks. I must have missed that scenario. Filed https://github.com/dotnet/roslyn/issues/46397
I mean, you can see why this looks odd when you place it next to other initializations
It's unclear why you would do that. If you're using 'var' and 'specifying the type on the right' then i would expect you would continue doing that.
But var
only works for inline variables? I thought that was one of the things this PR tries to solve.
So this isn't currently valid, afaik?
class MyClass
{
private var color = new Color("#FFF");
}
By consistency, I meant, why can't that just be valid, like it is for inline variables?
Why do we need a different syntax to initialize members without repeating the type?
csharplang
. (https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md)Compiler (general test plan for reference):
TestInLocal_LangVersion8
ArgList
, coversnew(__arglist)
and variants)TestNullableType01
,TestNullableType02
)TestInParameterDefaultValue
), in params (ArrayTypeInferredFromParams
)InAttributeParameter
)InStringInterpolation
)TestInAwait
, no target-type)TestBadTargetType_Cast
)TestInParameterDefaultValue
, not a constant)TestBestType_Lambda
), in async lambda (TestInAsyncLambda_01
)+new()
, ...), equality (1 == new()
), compound (x += new()
), ternary (x ? y : new()
and vice-versa)using
(InUsing01
), innameof()
(InNameOf
), intypeof()
(InTypeOf
), insizeof()
(InSizeOf
), inforeach
(InForeach
), in deconstruction (TestDeconstruction
,var (x, y) = new();
), inswitch
(InSwitch1
), in patterns (InIsOperator
), inif
(ConditionalOnNew
), inwhile
(ConditionalOnNew
), infixed
(InFixed
), as an expression-statement (TestInStatement
,new();
), inlock
(InLock
)InvocationOnDynamic
, no target-type)TestInClassInitializer
)ReturningFromAsyncMethod
)YieldReturn
)var
(TestTargetType_Var
), to nullable type (TestNullableType1
),TestAssignmentToClass
,TestAssignmentToStruct
, TODO currently shows a natural type)new()
andstruct
constraint (TestTypeParameter
,TestTypeParameter_ErrorCases
)new() { 42 }
(TestObjectAndCollectionInitializer
)TestObjectAndCollectionInitializer
,new() { 1, 2, 3 }
)new C() { x = new() }
), collection initializer (new C() { new D(1), new D(2), new() }
) (TestObjectAndCollectionInitializer
)TestTargetType_Enum
), array type, pointer type (TestTargetType_Pointer
, error), test in anonymous type (TestTargetType_AnonymousType
, error), tuple (TestTargetType_TupleType
, okay)RefReturnValue1
,RefReturnValue2
)ImplicitlyTypedArray
, no target-type)TestAssignment
,new() = 1;
)??=
(TestInNullCoalescingAssignment
), async-streams, nullability, Ranges (InRange
)IDE:
new()
(SpacingInTargetTypedNew
)(string a, string b)[] ab = new (string a, string b)[1];
vs.C c = new();
new
new()
visible by hovering, when debugging?new()
should go to the proper constructorUseImplicitType
andUseExplicitType
(for example,List<string> list = new();
should never offer to make the declarationvar
)Open LDM questions:
new[]
(https://github.com/dotnet/csharplang/issues/2701#issuecomment-533501825)Initial PR: https://github.com/dotnet/roslyn/pull/25196 Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md Championed issue (with LDM history): https://github.com/dotnet/csharplang/issues/100
Relates to https://github.com/dotnet/roslyn/issues/37821 (spec issues with target-typing)