dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.05k stars 4.04k forks source link

Test plan for "target-typed new" #28489

Open jcouv opened 6 years ago

jcouv commented 6 years ago

Compiler (general test plan for reference):

IDE:

Open LDM questions:

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)

alrz commented 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.

alrz commented 6 years ago
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.

jcouv commented 6 years ago

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):

As a side note, I've updated the proposal for default to list LDM meeting notes, if that helps.

alrz commented 6 years ago

Do we want to only permit equality (user-defined and built-in) among comparison and arithmetic operators?

jcouv commented 6 years ago

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.

alrz commented 6 years ago

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)

jcouv commented 6 years ago

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.

alrz commented 6 years ago

Not sure if this is already included, but we'll need to adjust type inferrer here.

https://github.com/dotnet/roslyn/blob/1545a961fbabf035f385347c1d2c7d8320b0e731/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs#L410

Probably there's more, because Type is now TypeSyntax?.

CyrusNajmabadi commented 6 years ago

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?

alrz commented 6 years ago

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.

CyrusNajmabadi commented 6 years ago

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.

mindplay-dk commented 4 years ago

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?

CyrusNajmabadi commented 4 years ago

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.

SirIntruder commented 4 years ago

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?

jcouv commented 4 years ago

Thanks. I must have missed that scenario. Filed https://github.com/dotnet/roslyn/issues/46397

mindplay-dk commented 4 years ago

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?