Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test plan for "target-typed new" #28489

Open
37 of 50 tasks
jcouv opened this issue Jul 12, 2018 · 16 comments
Open
37 of 50 tasks

Test plan for "target-typed new" #28489

jcouv opened this issue Jul 12, 2018 · 16 comments
Assignees
Labels
Area-Compilers Feature - Target-Typed New `new (args)` gets the created type inferred from context Feature Request Test Test failures in roslyn-CI Test-Gap Describes a specific feature or scenario that does not have test coverage
Milestone

Comments

@jcouv
Copy link
Member

jcouv commented Jul 12, 2018

Compiler (general test plan for reference):

  • LangVersion (TestInLocal_LangVersion8
  • arglist (ArgList, covers new(__arglist) and variants)
  • target-typing a nullable type (TestNullableType01, TestNullableType02)
  • as default parameter value (TestInParameterDefaultValue), in params (ArrayTypeInferredFromParams)
  • in attribute argument (InAttributeParameter)
  • struct with a 1-constructor, but no 0-constructor
  • in string interpolation (InStringInterpolation)
  • in await (TestInAwait, no target-type)
  • with explicit cast (TestBadTargetType_Cast)
  • is it possible to have a constant value? (TestInParameterDefaultValue, not a constant)
  • returning in lambda (with single or multiple returns, TestBestType_Lambda), in async lambda (TestInAsyncLambda_01)
  • with unary operators (+new(), ...), equality (1 == new()), compound (x += new()), ternary (x ? y : new() and vice-versa)
  • in using (InUsing01), in nameof() (InNameOf), in typeof() (InTypeOf), in sizeof() (InSizeOf), in foreach (InForeach), in deconstruction (TestDeconstruction, var (x, y) = new();), in switch (InSwitch1), in patterns (InIsOperator), in if (ConditionalOnNew), in while (ConditionalOnNew), in fixed (InFixed), as an expression-statement (TestInStatement, new();), in lock (InLock)
  • invoking a dynamic method (InvocationOnDynamic, no target-type)
  • as initial value for an auto-property (TestInClassInitializer)
  • returning in async method (ReturningFromAsyncMethod)
  • returning from IEnumerable-returning method
  • yielding from iterator method (YieldReturn)
  • assign to var (TestTargetType_Var), to nullable type (TestNullableType1),
  • semantic model (TestAssignmentToClass, TestAssignmentToStruct, TODO currently shows a natural type)
  • type parameter with and without new() and struct constraint (TestTypeParameter, TestTypeParameter_ErrorCases)
  • IOperation
  • new() { 42 } (TestObjectAndCollectionInitializer)
  • test with collection initializer (TestObjectAndCollectionInitializer, new() { 1, 2, 3 })
  • in object initializer (new C() { x = new() }), collection initializer (new C() { new D(1), new D(2), new() }) (TestObjectAndCollectionInitializer)
  • test with enum type (TestTargetType_Enum), array type, pointer type (TestTargetType_Pointer, error), test in anonymous type (TestTargetType_AnonymousType, error), tuple (TestTargetType_TupleType, okay)
  • ref local
  • ref return (RefReturnValue1, RefReturnValue2)
  • in implicitly-typed array (ImplicitlyTypedArray, no target-type)
  • use as L-value (TestAssignment, new() = 1;)
  • update the compiler test plan
    -[ ] test interaction with any C# 8.0 feature that was merged earlier: ??= (TestInNullCoalescingAssignment), async-streams, nullability, Ranges (InRange)

IDE:

Open LDM questions:

Initial PR: #25196
Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md
Championed issue (with LDM history): dotnet/csharplang#100

Relates to #37821 (spec issues with target-typing)

@jcouv jcouv added this to the 16.0 milestone Jul 12, 2018
@jcouv jcouv self-assigned this Jul 12, 2018
@alrz
Copy link
Member

alrz commented Aug 8, 2018

@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
Copy link
Member

alrz commented Aug 8, 2018

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
Copy link
Member Author

jcouv commented Aug 8, 2018

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, probably should fail because new() isn't allowed to take tuple type, looks like LDM allowed it after all)
  • case new(): (disallow since not constant)

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

@alrz
Copy link
Member

alrz commented Aug 8, 2018

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

@jcouv
Copy link
Member Author

jcouv commented Aug 8, 2018

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
Copy link
Member

alrz commented Aug 8, 2018

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
Copy link
Member Author

jcouv commented Aug 8, 2018

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
Copy link
Member

alrz commented Oct 18, 2018

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

var info = SemanticModel.GetSymbolInfo(creation.Type, CancellationToken);

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

@CyrusNajmabadi
Copy link
Member

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
Copy link
Member

alrz commented Oct 20, 2018

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
Copy link
Member

CyrusNajmabadi commented Oct 20, 2018

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.

@jcouv jcouv modified the milestones: 16.0, 16.0.P2 Nov 12, 2018
@gafter gafter added the Feature - Target-Typed New `new (args)` gets the created type inferred from context label Dec 15, 2018
@jcouv jcouv modified the milestones: 16.0.P2, 16.0 Dec 21, 2018
@jcouv jcouv modified the milestones: 16.0, 16.1 Apr 18, 2019
@mindplay-dk
Copy link

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
Copy link
Member

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
Copy link

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
Copy link
Member Author

jcouv commented Jul 28, 2020

Thanks. I must have missed that scenario. Filed #46397

@mindplay-dk
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature - Target-Typed New `new (args)` gets the created type inferred from context Feature Request Test Test failures in roslyn-CI Test-Gap Describes a specific feature or scenario that does not have test coverage
Projects
Status: Done Umbrellas
Development

No branches or pull requests

7 participants