This is an archive of the discontinued LLVM Phabricator instance.

[clang-format] Add a new AfterCSharpProperty to BraceWrapping
AcceptedPublic

Authored by MyDeveloperDay on Apr 16 2023, 8:18 AM.

Details

Summary

This change allows always breaking before braces on C# setter and getter properties

Fixes #61968

https://github.com/llvm/llvm-project/issues/61968

Diff Detail

Event Timeline

MyDeveloperDay created this revision.Apr 16 2023, 8:18 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptApr 16 2023, 8:18 AM
MyDeveloperDay requested review of this revision.Apr 16 2023, 8:18 AM
MyDeveloperDay added inline comments.
clang/unittests/Format/FormatTestCSharp.cpp
1644

Hmm.. maybe this should be

string Foo 
{
    set;get
}

I'm not convinced AfterCSharpProperty is a good name, open for suggestions

for default set;get or get;set for when AfterCSharpProperty is true,

public Foo {
    set;
    get;
}

re clang-format

I'm not convinced AfterCSharpProperty is a good name, open for suggestions

I've not enough domain knowledge to name it.

clang/include/clang/Format/Format.h
1269

Please sort. :)

4327

It's missing in operator==. And I think we should add operator== to BraceWrappingFlags and use that here.

clang/lib/Format/Format.cpp
185

A bit up.

clang/unittests/Format/FormatTestCSharp.cpp
1644

I'm no C# guy, but when we split the lines, I think get and set should be on it's own line.

clang/include/clang/Format/Format.h
1269

Are we sure we want THIS to be alphabetic, as this changes the initializer order, if someone is using the format() library in downstream code this could subtly break them?

Expanded.BraceWrapping = {/*AfterCaseLabel=*/false,
                            /*AfterClass=*/false,
                            /*AfterControlStatement=*/FormatStyle::BWACS_Never,
                            /*AfterEnum=*/false,
                            /*AfterFunction=*/false,
                            /*AfterNamespace=*/false,
                            /*AfterObjCDeclaration=*/false,
                            /*AfterStruct=*/false,
                            /*AfterUnion=*/false,
                            /*AfterExternBlock=*/false,
                            /*BeforeCatch=*/false,
                            /*BeforeElse=*/false,
                            /*BeforeLambdaBody=*/false,
                            /*BeforeWhile=*/false,
                            /*IndentBraces=*/false,
                            /*SplitEmptyFunction=*/true,
                            /*SplitEmptyRecord=*/true,
                            /*SplitEmptyNamespace=*/true,
                            /*AfterCSharpProperty=*/false};
owenpan added inline comments.Apr 17 2023, 12:40 AM
clang/lib/Format/TokenAnnotator.cpp
5047–5050
clang/unittests/Format/FormatTestCSharp.cpp
1625

Ditto below.

clang/include/clang/Format/Format.h
1269

I'd say yes, we are breaking this stuff always.

Granted this one may compile without an error, but they should get a warning of a missing initializer.

You could add a constructor to initialize out of struct order.

MyDeveloperDay added inline comments.Apr 17 2023, 2:53 AM
clang/include/clang/Format/Format.h
1269

I'm ok with making the change, just wanted to double check that we are ok to break the ordering.

owenpan added inline comments.Apr 17 2023, 3:54 PM
clang/include/clang/Format/Format.h
1269

I kept them sorted in D52527 years ago. :)

MyDeveloperDay marked 8 inline comments as done.

Address review comments, still would like to solve the indenting issue.

MyDeveloperDay planned changes to this revision.EditedApr 18 2023, 5:18 AM

need to handle init;
need to handle public|private|internal accessors

StrangeRanger added a subscriber: StrangeRanger.EditedApr 18 2023, 9:44 AM

for default set;get or get;set for when AfterCSharpProperty is true,

public Foo {
    set;
    get;
}

At least from my experience, the getter is specified before the setter, though I'm unsure how important this is in your eyes.
If we assume the above sentence is always true, and the brace doesn't break after the property name, the following are (some of) the options in formatting that I've seen:

When autoproperty is used:

public Foo {
    get;
    set;
}

// OR

public FooTwo { set; get; }  // What I prefer.

When a non-autoproperty is used:

/* When only one thing is provided in the getter and setter. */

public Foo {
    get { return _next; }  
    set { _next = value; }
}  // What I prefer.

// OR

public Foo {
    get => return _next; 
    set => _next = value;
}  // Another option that I like (which really doesn't apply to this code change).

// OR

public Foo {
    get 
    { 
        return _next; 
    }  

    set 
    { 
        _next = value; 
    }
}

// ----------------------------------

/* When more than one thing is provided in the getter and setter. */

public Foo {
    get 
    { 
        // ...
        return _next; 
    }  

    set 
    { 
        _next = value;
        // ...
    }
}

Please note that the above examples *slightly* ignore the different variations in how the braces could be set (new line or not).

I hope this input is enough/helps. Please let me know if you'd like any clarification.

MyDeveloperDay added a comment.EditedApr 18 2023, 9:59 AM
public Foo {
    set;
    get;
}

At least from my experience, the getter is specified before the setter, though I'm unsure how important this is in your eyes.

Lets hold off the idea of swapping set;get around from this patch, but this is something that I think we could do.. (using the FormatTokenLexer.cpp to swap trivial set;get into get;set but lets leave that for another feature, as it would be code altering)

honeslty I think we need another option maybe

enum AllowShortCSharpProperties {
      Leave,
      Never,
      Empty,
      Always
}

Leave = Don't touch them
Never = always break (braces follow AfterCSharpProperty

set;
get;

set { 
  val = value; 
}
get { 
  return = value; 
}

Empty = only one the same line when empty

set; get

set { 
   val = value; 
}
get { 
  return = value;
}

Always = always short form for trivial

set; get;

set {  val = value; }
get {  return = value; }

I'm trying to decide if I put that option in this change or in a seperate change (thoughts @HazardyKnusperkeks, @owenpan )

FYI, I've solved the indention issue in my branch which has been broken forever from what I can tell.

verifyFormat("class A\n"
             "{\n"
             "    string Bar {\n"
             "        get;\n"
             "        set\n"
             "        {\n"
             "            val = value;\n"
             "        }\n"
             "    }\n"
             "}",
             Style);

add init support and fix indentation issue when only one property is defined but the is an auto-property

upload the correct patch file

StrangeRanger added a comment.EditedApr 18 2023, 10:41 AM

I'm trying to decide if I put that option in this change or in a separate change (thoughts @HazardyKnusperkeks, @owenpan )

I'm not too privy to your workflow, but if by "in a separate change" you mean like "in a different PR", I'd probably say implement it in a different change, for the sake of revision history.

public Foo {
    set;
    get;
}

At least from my experience, the getter is specified before the setter, though I'm unsure how important this is in your eyes.

Lets hold off the idea of swapping set;get around from this patch, but this is something that I think we could do.. (using the FormatTokenLexer.cpp to swap trivial set;get into get;set but lets leave that for another feature, as it would be code altering)

honeslty I think we need another option maybe

enum AllowShortCSharpProperties {
      Leave,
      Never,
      Empty,
      Always
}

Leave = Don't touch them
Never = always break (braces follow AfterCSharpProperty

set;
get;

set { 
  val = value; 
}
get { 
  return = value; 
}

Empty = only one the same line when empty

set; get

set { 
   val = value; 
}
get { 
  return = value;
}

Always = always short form for trivial

set; get;

set {  val = value; }
get {  return = value; }

I'm trying to decide if I put that option in this change or in a seperate change (thoughts @HazardyKnusperkeks, @owenpan )

FYI, I've solved the indention issue in my branch which has been broken forever from what I can tell.

verifyFormat("class A\n"
             "{\n"
             "    string Bar {\n"
             "        get;\n"
             "        set\n"
             "        {\n"
             "            val = value;\n"
             "        }\n"
             "    }\n"
             "}",
             Style);

I'd say do it now. No need to introduce a new option, when we want to change it directly.

Or at least make it the enum right now, with just 2 values.

There is more to this than meets the eye.. what we have so far, from existing AfterFunction use and the propsed here AfterCSharpProperty is...

public Foo
{                        <--- controlled by **AfterFunction **(rightly or wrongly)
     get {               <--- proposing to controlled by **AfterCSharpProperty**
        return;
     }
     set {
        val= value;
     }
}

But for trivial Properties, these are automatically assumed to be wanted on the same line.

public Foo { set; get}

Trivial Properties are defined as:

 all ordering variants  (public/internal/private)
set;get;
public set;get
public init;get
get;set

My problem is AfterFunction ONLY kicks in if this isn't a TrivialProperty.. We can't currently force TrivialProperties to be handled differently.

AfterFunction = true we get

public Foo { set; get}  

// we can't get to
public Foo { 
  set; get
}

Handling of Trivial Properties I feel there should be an "AllowCSharpPropertiesOnASingleLine" to match other options, otherwise it should follow AfterFunction (AllowCSharpPropertiesOnASingleLine default would need to be true to match current behaviour)

AllowCSharpPropertiesOnASingleLine =true, AfterFunction = true/false

public Foo {  set; get }

AllowCSharpPropertiesOnASingleLine = false, AfterFunction = true

public Foo { 
     set; get
}

So then how do we handle

set;get

vs

set;
get;

Should AfterCSharpProperty be use in this case? or do we introduce "BreakBetweenCSharpProperties " (default false)

I know it seems strange to add 3 options to handle this, but I think it would give people the best of both worlds

you can have the following in the same file (which is actually quite common)

BreakBetweenCSharpProperties = false
AllowCSharpPropertiesOnASingleLine = true
AfterFunction = true
AfterCSharpProperty = true

public Foo
{                        
     get 
     {               
          return value;
     }
     set {
          value = 123;
     }
}

public Bar
{                        
     get; set;
}

If we useAfterCSharpProperty to control BreakBetweenCSharpProperties then I think we have to have the following and we can't control Bar how we might want

public Foo
{                        
     get 
     {               
          return value;
     }
     set {
          value = 123;
     }
}

public Bar
{                        
     get; 
     set;
}

From what I can tell...Microsoft .NET Framework seems to be

BreakBetweenCSharpProperties = true
AllowCSharpPropertiesOnASingleLine = false
AfterFunction = true
AfterCSharpProperty = true

https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/iappdomainsetup.cs

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/using-properties

It would seem they don't seem to put Trivial Properties/AutoProperties on the same line

Adds additional options to give us much greater control over how we format C# properties especially auto properties

(Submitting this for safe keeping, I need to try and minimize the if expression, I'm sure it could be simplified into a few less clauses)

Sorry had a clang-format reflow comments moment, revert the unrelated comment changes
simplify the if's a little

In my C# project, these settings give me just what we tend to use.

BreakBeforeBraces: Custom
BraceWrapping:
  AfterFunction: true
  AfterCSharpProperty: true
AllowShortCSharpPropertiesOnASingleLine: false
AlwaysBreakBetweenShortCSharpProperties: false

for AutoProperties

public int min_value
{
    get; set;
}

but for formal properties,

public int Margin
{
    get
    {
        return this.value> 0? 10 : 0;
    }
}

although I like the idea of being able to change this to this with AfterCSharpProperty: false

public int Margin
{
    get {
        return this.value> 0? 10 : 0;
    }
}
clang/lib/Format/TokenAnnotator.cpp
21

What is this?

5023–5026

Isn't the example wrong?

5043
5051
5058

This gets keeping repeated, maybe put it in a own function?

5065–5066

Same here.

5104

Drop

5112–5114

A bit too much parens.

5124

Also not needed.

clang/lib/Format/UnwrappedLineParser.cpp
2076–2091
MyDeveloperDay marked 10 inline comments as done.

Address review comment, add convenience functions to simplify conditions

MyDeveloperDay planned changes to this revision.Apr 20 2023, 8:08 AM

There is another case I need to cover

Style.BraceWrapping.AfterCSharpProperty = false;
Style.AllowShortCSharpPropertiesOnASingleLine = false;
Style.AlwaysBreakBetweenShortCSharpProperties = false;
Style.BraceWrapping.AfterFunction = true;

verifyFormat("class A\n"
               "{\n"
               "    string Bar1\n"
               "    {\n"
               "        set; get;\n"
               "    }\n"
               "}",
               Style);

This is currently formatting as

class A
{
    string Bar1 
    { set; get; }
}

which doesn't feel quite right.

clang/lib/Format/TokenAnnotator.cpp
21

huh! I've no idea? where did that come from?

Handle the case

string Foo 
{
     set;get;
}
This revision is now accepted and ready to land.Apr 21 2023, 12:28 AM

Just an update on this, it works well but some of the cases are not tolerant to comments, I'm working my way through those issues