dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
290 stars 64 forks source link

Way to express "Simple" Expression Bodied Members #61

Open AdamSpeight2008 opened 7 years ago

AdamSpeight2008 commented 7 years ago

Auto ReadOnly Properties are convenient for when we can express our intention at initialization

Friend Overrides ReadOnly Property CanConstruct As Boolean = False

Sometime I like it to just as convenient to express what should happen at invocation, especially if it just a simple express / statement (IE a single Return / Throw ).

Friend Overrides ReadOnly Property CanConstruct As Boolean => Throw New NotSupportedException()
Friend Overrides ReadOnly Property CanConstruct As Boolean => Throw New NotImplementedException()

It would reduce the amount you'd have to read and type 80% per a "simple" property.

reduckted commented 7 years ago

I find these types of expressions to be far less readable than a normal method/property. I realise that it's subjective though.

As for less typing, the IDE already does most of the work for you. You just have to type Get, press enter, and the IDE will insert the two End statements.

AnthonyDGreen commented 7 years ago

I'm hesitant to add new syntax to support the scenario of property stubs for properties you just haven't implemented yet. Can you provide more examples?

scottdorman commented 7 years ago

@AnthonyDGreen Is this not just a VB equivalent of C# 7's expression bodied members and throw expressions? I think the original code shown (throwing NotImplementedException) was just as an example of the syntax.

AdamSpeight2008 commented 7 years ago

NotImplementedException is already provide via auto-completion on overriddes property.

Public MustInherit Class Base
    MustOverride ReadOnly Property Value As Int32
    Overridable ReadOnly Property Text As String = "Base"
End Class

Public Class F : Inherits Base

    Public Overrides ReadOnly Property Value As Integer
        Get
            Throw New NotImplementedException()
        End Get
    End Property
End Class

If we could express that like auto-implemented properties.

Public Class F : Inherits Base
    Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()
End Class

If we override the Value property, auto-completion provides the simple call to the base implementaion.

Public Class F : Inherits Base
    Public Overrides ReadOnly Property Text As String => "First"
    Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()
End Class

12 Loc down to 4 Loc is a big reduction of syntax ceremony, and it just a simple to read. IMHO

AdamSpeight2008 commented 7 years ago

I've refactored SyntaxNode as an real-world example utilising this feature.

' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Runtime.CompilerServices
Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax

Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax

    <DebuggerDisplay("{GetDebuggerDisplay(), nq}")>
    Partial Friend Class VisualBasicSyntaxNode
        Inherits GreenNode

        Friend ReadOnly Property Kind As SyntaxKind => Return CType(Me.RawKind, SyntaxKind)

        Friend ReadOnly Property ContextualKind As SyntaxKind => Return CType(Me.RawContextualKind, SyntaxKind)

        Public Overrides ReadOnly Property KindText As String => Return Me.Kind.ToString()

        Public Overrides ReadOnly Property Language As String => Return LanguageNames.VisualBasic

        ''' <summary>Should only be called during construction.</summary>
        ''' <remarks>This should probably be an extra constructor parameter, but we don't need more constructor overloads.</remarks>
        Protected Sub SetFactoryContext(context As ISyntaxFactoryContext)
            If context.IsWithinAsyncMethodOrLambda Then SetFlags(NodeFlags.FactoryContextIsInAsync)
            If context.IsWithinIteratorContext Then SetFlags(NodeFlags.FactoryContextIsInIterator)
        End Sub

        Friend Function MatchesFactoryContext(context As ISyntaxFactoryContext) As Boolean
            Return context.IsWithinAsyncMethodOrLambda = Me.ParsedInAsync AndAlso
                   context.IsWithinIteratorContext = Me.ParsedInIterator
        End Function

#Region "Serialization"
        Friend Sub New(reader As ObjectReader)
            MyBase.New(reader)
        End Sub
#End Region

        ' The rest of this class is just a convenient place to put some helper functions that are shared by the 
        ' various subclasses.

        Public Overrides ReadOnly Property IsStructuredTrivia As Boolean => Return TypeOf Me Is StructuredTriviaSyntax

        Public Overrides ReadOnly Property IsDirective As Boolean => Return TypeOf Me Is DirectiveTriviaSyntax

        Public Overrides ReadOnly Property IsSkippedTokensTrivia As Boolean => Return Me.Kind = SyntaxKind.SkippedTokensTrivia

        Public Overrides ReadOnly Property IsDocumentationCommentTrivia As Boolean => Return Me.Kind = SyntaxKind.DocumentationCommentTrivia

        Protected Overrides Function GetSlotCount() As Integer
            Throw ExceptionUtilities.Unreachable
        End Function

        Protected Property _slotCount As Integer
            Get
                Return Me.SlotCount
            End Get

            Set(value As Integer)
                Me.SlotCount = value
            End Set
        End Property

        Friend Function GetFirstToken() As SyntaxToken
            Return DirectCast(Me.GetFirstTerminal(), SyntaxToken)
        End Function

        Friend Function GetLastToken() As SyntaxToken
            Return DirectCast(Me.GetLastTerminal(), SyntaxToken)
        End Function

        ' Get the leading trivia a green array, recursively to first token.
        Friend Overridable Function GetLeadingTrivia() As GreenNode
            Dim possibleFirstChild = GetFirstToken()
            If possibleFirstChild Is Nothing Then Return Nothing
            Return possibleFirstChild.GetLeadingTrivia()
        End Function

        Public Overrides Function GetLeadingTriviaCore() As GreenNode
            Return Me.GetLeadingTrivia()
        End Function

        ' Get the trailing trivia a green array, recursively to first token.
        Friend Overridable Function GetTrailingTrivia() As GreenNode
            Dim possibleLastChild = GetLastToken()
            If possibleLastChild Is Nothing Then Return Nothing
            Return possibleLastChild.GetTrailingTrivia()
        End Function

        Public Overrides Function GetTrailingTriviaCore() As GreenNode
            Return Me.GetTrailingTrivia()
        End Function

        Protected Sub New(kind As SyntaxKind)
            MyBase.New(CType(kind, UInt16))
            GreenStats.NoteGreen(Me)
        End Sub

        Protected Sub New(kind As SyntaxKind, width As Integer)
            MyBase.New(CType(kind, UInt16), width)
            GreenStats.NoteGreen(Me)
        End Sub

        Protected Sub New(kind As SyntaxKind, errors As DiagnosticInfo())
            MyBase.New(CType(kind, UInt16), errors)
            GreenStats.NoteGreen(Me)
        End Sub

        Protected Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), width As Integer)
            MyBase.New(CType(kind, UInt16), errors, width)
            GreenStats.NoteGreen(Me)
        End Sub

        Friend Sub New(kind As SyntaxKind, diagnostics As DiagnosticInfo(), annotations As SyntaxAnnotation())
            MyBase.New(CType(kind, UInt16), diagnostics, annotations)
            GreenStats.NoteGreen(Me)
        End Sub

        Friend Sub New(kind As SyntaxKind, diagnostics As DiagnosticInfo(), annotations As SyntaxAnnotation(), fullWidth As Integer)
            MyBase.New(CType(kind, UInt16), diagnostics, annotations, fullWidth)
            GreenStats.NoteGreen(Me)
        End Sub

        ''' <summary>
        ''' Get all syntax errors associated with this node, or any child nodes, grand-child nodes, etc. The errors
        ''' are not in order.
        ''' </summary>
        Friend Overridable Function GetSyntaxErrors() As IList(Of DiagnosticInfo)
            If Not ContainsDiagnostics Then Return Nothing

            Dim accumulatedErrors As New List(Of DiagnosticInfo)
            AddSyntaxErrors(accumulatedErrors)
            Return accumulatedErrors
        End Function

        Friend Overridable Sub AddSyntaxErrors(accumulatedErrors As List(Of DiagnosticInfo))
            If Me.GetDiagnostics IsNot Nothing Then
                accumulatedErrors.AddRange(Me.GetDiagnostics)
            End If

            Dim cnt = SlotCount()

            If cnt = 0 Then Return
            For i As Integer = 0 To cnt - 1
                Dim child = GetSlot(i)
                If child IsNot Nothing AndAlso child.ContainsDiagnostics Then
                   DirectCast(child, VisualBasicSyntaxNode).AddSyntaxErrors(accumulatedErrors)
                End If
            Next
        End Sub

        Private Function GetDebuggerDisplay() As String
            Dim text = ToFullString()
            If text.Length > 400 Then text = text.Substring(0, 400)
            Return Kind.ToString & ":" & text
        End Function

        Friend Overloads Shared Function IsEquivalentTo(left As VisualBasicSyntaxNode, right As VisualBasicSyntaxNode) As Boolean
            If left Is right Then Return True
            If left Is Nothing OrElse right Is Nothing Then Return False
            Return left.IsEquivalentTo(right)
        End Function

        ' Use conditional weak table so we always return same identity for structured trivia
        Private Shared ReadOnly s_structuresTable As New ConditionalWeakTable(Of SyntaxNode, Dictionary(Of Microsoft.CodeAnalysis.SyntaxTrivia, SyntaxNode))

        Public Overrides Function GetStructure(trivia As Microsoft.CodeAnalysis.SyntaxTrivia) As SyntaxNode
            If Not trivia.HasStructure Then Return Nothing
            Dim parent = trivia.Token.Parent
            If parent Is Nothing Then Return VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia)

            Dim [structure] As SyntaxNode = Nothing
            Dim structsInParent = s_structuresTable.GetOrCreateValue(parent)

            SyncLock structsInParent
                If Not structsInParent.TryGetValue(trivia, [structure]) Then
                    [structure] = VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia)
                    structsInParent.Add(trivia, [structure])
                End If
            End SyncLock

            Return [structure]
        End Function

        Public Overrides Function CreateSeparator(Of TNode As SyntaxNode)(element As SyntaxNode) As CodeAnalysis.SyntaxToken
            Dim separatorKind As SyntaxKind = SyntaxKind.CommaToken
            If element.Kind = SyntaxKind.JoinCondition Then
                separatorKind = SyntaxKind.AndKeyword
            End If
            Return VisualBasic.SyntaxFactory.Token(separatorKind)
        End Function

        Public Overrides Function IsTriviaWithEndOfLine() As Boolean
            Return Me.Kind = SyntaxKind.EndOfLineTrivia OrElse Me.Kind = SyntaxKind.CommentTrivia
        End Function

    End Class

End Namespace
AdamSpeight2008 commented 7 years ago

And SyntaxLiterals

' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
    Friend Partial Class CharacterLiteralTokenSyntax
        Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
    End Class

    Friend Partial Class DateLiteralTokenSyntax
        Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
    End Class

    Friend Partial Class DecimalLiteralTokenSyntax
        Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
    End Class

    Friend Partial Class StringLiteralTokenSyntax
        Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
        Friend NotOverridable Overrides ReadOnly Property ValueText As String => Return Me.Value
    End Class

    Friend NotInheritable Class IntegerLiteralTokenSyntax(Of T)
        Inherits IntegerLiteralTokenSyntax

        Friend ReadOnly _value As T

        Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter, value As T)
            MyBase.New(kind, text, leadingTrivia, trailingTrivia, base, typeSuffix)
            Me._value = value
        End Sub

        Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter, value As T)
            MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia, base, typeSuffix)
            Me._value = value
        End Sub

        Friend Sub New(reader As ObjectReader)
            MyBase.New(reader)
            Me._value = CType(reader.ReadValue(), T)
        End Sub

        Shared Sub New()
            ObjectBinder.RegisterTypeReader(GetType(IntegerLiteralTokenSyntax(Of T)), Function(r) New IntegerLiteralTokenSyntax(Of T)(r))
        End Sub

        Friend Overrides Sub WriteTo(writer As ObjectWriter)
            MyBase.WriteTo(writer)
            writer.WriteValue(Me._value)
        End Sub

        ''' <summary>The value of the token.</summary>
        Friend ReadOnly Property Value As T => Return Me._value
        Friend Overrides ReadOnly Property ValueText As String => Return _value.ToString
        Friend Overrides ReadOnly Property ObjectValue As Object => Return Me.Value

        Public Overrides Function WithLeadingTrivia(trivia As GreenNode) As GreenNode
            Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, trivia, GetTrailingTrivia, _base, _typeSuffix, _value)
        End Function

        Public Overrides Function WithTrailingTrivia(trivia As GreenNode) As GreenNode
            Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, GetLeadingTrivia, trivia, _base, _typeSuffix, _value)
        End Function

        Friend Overrides Function SetDiagnostics(newErrors As DiagnosticInfo()) As GreenNode
            Return New IntegerLiteralTokenSyntax(Of T)(Kind, newErrors, GetAnnotations, Text, GetLeadingTrivia, GetTrailingTrivia, _base, _typeSuffix, _value)
        End Function

        Friend Overrides Function SetAnnotations(annotations As SyntaxAnnotation()) As GreenNode
            Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, annotations, Text, GetLeadingTrivia, GetTrailingTrivia, _base, _typeSuffix, _value)
        End Function
    End Class

    ''' <summary>
    ''' Represents an integer literal token.
    ''' </summary>
    Friend MustInherit Class IntegerLiteralTokenSyntax
        Inherits SyntaxToken

        Friend ReadOnly _base As LiteralBase
        Friend ReadOnly _typeSuffix As TypeCharacter

        Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter)
            MyBase.New(kind, text, leadingTrivia, trailingTrivia)
            Me._base = base
            Me._typeSuffix = typeSuffix
        End Sub

        Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter)
            MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia)
            Me._base = base
            Me._typeSuffix = typeSuffix
        End Sub

        Friend Sub New(reader As ObjectReader)
            MyBase.New(reader)
            Me._base = CType(reader.ReadByte(), LiteralBase)
            Me._typeSuffix = CType(reader.ReadByte(), TypeCharacter)
        End Sub

        Friend Overrides Sub WriteTo(writer As ObjectWriter)
            MyBase.WriteTo(writer)
            writer.WriteByte(CType(Me._base, Byte))
            writer.WriteByte(CType(Me._typeSuffix, Byte))
        End Sub

        ''' <summary>Whether the token was specified in base 10, 16, 8, or 2.</summary>
        Friend ReadOnly Property Base As LiteralBase => Return Me._base

        ''' <summary>
        ''' The type suffix or type character that was on the literal, if any. If no suffix
        ''' was present, TypeCharacter.None is returned.
        ''' </summary>
        Friend ReadOnly Property TypeSuffix As TypeCharacter => Return Me._typeSuffix

    End Class

    ''' <summary>
    ''' Represents an floating literal token.
    ''' </summary>
    Friend NotInheritable Class FloatingLiteralTokenSyntax(Of T)
        Inherits FloatingLiteralTokenSyntax

        Friend ReadOnly _value As T

        Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter, value As T)
            MyBase.New(kind, text, leadingTrivia, trailingTrivia, typeSuffix)
            Me._value = value
        End Sub

        Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter, value As T)
            MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia, typeSuffix)
            Me._value = value
        End Sub

        Friend Sub New(reader As ObjectReader)
            MyBase.New(reader)
            Me._value = CType(reader.ReadValue(), T)
        End Sub

        Shared Sub New()
            ObjectBinder.RegisterTypeReader(GetType(FloatingLiteralTokenSyntax(Of T)), Function(r) New FloatingLiteralTokenSyntax(Of T)(r))
        End Sub

        Friend Overrides Sub WriteTo(writer As ObjectWriter)
            MyBase.WriteTo(writer)
            writer.WriteValue(Me._value)
        End Sub

        ''' <summary>The value of the token.</summary>
        Friend ReadOnly Property Value As T => Return Me._value

        Friend Overrides ReadOnly Property ValueText As String => Return _value.ToString

        Friend Overrides ReadOnly Property ObjectValue As Object => Return Me.Value

        Public Overrides Function WithLeadingTrivia(trivia As GreenNode) As GreenNode
            Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, trivia, GetTrailingTrivia, _typeSuffix, _value)
        End Function

        Public Overrides Function WithTrailingTrivia(trivia As GreenNode) As GreenNode
            Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, GetLeadingTrivia, trivia, _typeSuffix, _value)
        End Function

        Friend Overrides Function SetDiagnostics(newErrors As DiagnosticInfo()) As GreenNode
            Return New FloatingLiteralTokenSyntax(Of T)(Kind, newErrors, GetAnnotations, Text, GetLeadingTrivia, GetTrailingTrivia, _typeSuffix, _value)
        End Function

        Friend Overrides Function SetAnnotations(annotations As SyntaxAnnotation()) As GreenNode
            Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, annotations, Text, GetLeadingTrivia, GetTrailingTrivia, _typeSuffix, _value)
        End Function
    End Class

    ''' <summary>Represents an floating literal token.</summary>
    Friend MustInherit Class FloatingLiteralTokenSyntax
        Inherits SyntaxToken

        Friend ReadOnly _typeSuffix As TypeCharacter

        Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter)
            MyBase.New(kind, text, leadingTrivia, trailingTrivia)
            Me._typeSuffix = typeSuffix
        End Sub

        Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter)
            MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia)
            Me._typeSuffix = typeSuffix
        End Sub

        Friend Sub New(reader As ObjectReader)
            MyBase.New(reader)
            Me._typeSuffix = CType(reader.ReadByte(), TypeCharacter)
        End Sub

        Friend Overrides Sub WriteTo(writer As ObjectWriter)
            MyBase.WriteTo(writer)
            writer.WriteByte(CType(Me._typeSuffix, Byte))
        End Sub

        ''' <summary>
        ''' The type suffix or type character that was on the literal, if any. If no suffix
        ''' was present, TypeCharacter.None is returned.
        ''' </summary>
        Friend ReadOnly Property TypeSuffix As TypeCharacter => Return Me._typeSuffix

    End Class

End Namespace
scottdorman commented 7 years ago

Maybe it's because I'm already familiar with this from C#, but I see the two refactored examples @AdamSpeight2008 did as being more readable/understandable/concise. Particularly in the case of ReadOnly properties, it makes the amount of code you have to write almost trivial, certainly easier to express the intent.

xieguigang commented 7 years ago

@AdamSpeight2008 Using vb With keyword make this simple expression more vb style than using =>, example: https://github.com/dotnet/roslyn/issues/15708

If not implemented, then we can write this simple expression:

Public ReadOnly Property Example As Foo With New NotImplementedException
AdamSpeight2008 commented 7 years ago

@xieguigang
My feelings is that could issues it object initializers and with blocks

rskar-git commented 7 years ago

@xieguigang @AdamSpeight2008

I'm OK with =>, but instead of With, would it be more VB-ish to use Get?:

Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException()

Friend ReadOnly Property Kind As SyntaxKind Get Return CType(Me.RawKind, SyntaxKind)

It's a subtle point, but => is sort-of the same as >= (although the IDE will transform => into >=).

So, instead of => how about colon?:

Friend Overrides ReadOnly Property CanConstruct As Boolean : Throw New NotSupportedException()

Friend ReadOnly Property Kind As SyntaxKind : Return CType(Me.RawKind, SyntaxKind)
AdamSpeight2008 commented 7 years ago

@rskar-git Would the compiler complain ?

Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException()

Friend ReadOnly Property Kind As SyntaxKind Get Return CType(Me.RawKind, SyntaxKind)

about incomplete Get ... End Get block?

Friend Overrides ReadOnly Property CanConstruct As Boolean
  Get
    Throw New NotSupportedException()
  End Get
End Property

Friend ReadOnly Property Kind As SyntaxKind
  Get
    Return CType(Me.RawKind, SyntaxKind)
  End Get
End Property
rskar-git commented 7 years ago

@AdamSpeight2008

Would the compiler complain ... about incomplete Get ... End Get block?

(I hope I'm not misunderstanding your question.) I was merely suggesting Get in place of =>, all else stays the same, and so there is still no End Get in this case.

The Get of a proper Get ... End Get block must start on a new line; putting the Get on the same line as the ReadOnly Property definition already results in a syntax error. (Unless that's been changed in VS 2017.) So I wouldn't anticipate inventing a new syntax where Get is on the same line as ReadOnly Property should make for any kind of trouble.

But maybe I'm being naive. It just seems to me that ReadOnly Property ... Get <simple-expression> is somewhat analogous to the two modes of If ... Then or the new With syntax (New Foo() With { ... }) as far as syntax parsing may go. Is there something to the compiler's design that would make the use of Get in this way (as an alternative to =>) problematic?

As I said before, I'm OK with =>, but it would seem a rather lonely operator if `ReadOnly Property ... =>

` is the only use for it.
AdamSpeight2008 commented 7 years ago

@rskar-git My thinking on => is that would be easier to parse, ( a single char character lookup at =) rather than parsing a Get ... End Get block only to having it fail and require it to be reparsed as a inline-get.

rskar-git commented 7 years ago

@AdamSpeight2008

It's clear now I need to really get familiar with how the VB compiler actually works; I'm (not yet) at any place to say whether or not a reparse would be likely.

But you've got me wondering ... is that what happens now for New Foo() With { ... }? I.e., does the compiler first fail at finding an expected End With at some point then back-tracks in order to parse "with-clause-of-new-statement"?

edin commented 7 years ago

Would it possible to support writing as well, i think it would allow simpler implementations of various patterns.

Public ReadOnly Property Value As String => m_object.Value   
Public Property Value As String => m_object.Value    

Public ReadOnly Property Value As String => Throw New Exception()  
Public Property Value As String => Throw New Exception()  

Public Overrides ReadOnly Property Value As Integer  => 2 
Public Overrides Property Value As Integer => 2 ' Error

Private m_LastName As String = ""
Public Property FirstName As String => Me.Value("FirstName") 
Public Property LastName As String  => Me.Value(m_LastName)
Public Property Email As String     => Me.Value(NameOf(Me.Email))
Public Property BirthDate As Date   => Me.Value() 
Public Enum Data
    Address
    Email
    Phone
End Enum

Class Customer
    Inherits Model

    Private m_Id As Integer
    Private m_FirstName As Integer

    Property Id As Integer => MyBase.Value(m_Id)
    Property FirstName As String => MyBase.SetOrGet(m_FirstName)

    Property LastName As String => MyBase.Value
    Property BirthDate As Date => MyBase.Value

    Property Address As String => MyBase.Value(Data.Addres)
    Property Email As String => MyBase.Value(Data.Email)
    Property Phone As String => MyBase.Value(Data.Phone)
End Class

Class Model
    Public Property Value(Of T)(<CallerMemberName> propertyName As String) As T
    ...
    End Property

    Public Property Value(Of T)(data As Data) As T
    ...    
    End Property

    Private Property Value(Of T)(ByRef field As T, ByVal value) As T
    ...
    End Function

    'Or something like this since ByRef is not supported in properties
    Private Sub SetOrGet(Of T)(ByRef field As T, value As T, isGet As Boolean ) As T
    End Sub
End Class 
AdamSpeight2008 commented 7 years ago

@edin I've not looked into supporting Write, as I not thought out how to state the value being passed to the property.

Public Property Value As T
  Get
    Return _Value
  End Get
 ' Eg the 'newValue parameter of the setter
  Set( newValue As T )
    _Value = newValue
  End Set
End Property

Some languages reserve the name value to be used for the role. Doing that to VB would very predominately a breaking change. So need more thought time on it.

If you found a way, please don't be a scared to post your idea. As it may be the one we need.

reduckted commented 7 years ago

Some languages reserve the name value to be used for the role.

@AdamSpeight2008 Actually, VB already supports that. The argument list for a set accessor is optional. If it's left off, Value is assumed to be the argument.

Public Property Foo As Integer
    Get
        Return _foo
    End Get
    Set
        _foo = Value
    End Set
End Property

And yes, it still works if the property is called Value. 😄

edin commented 7 years ago

@AdamSpeight2008

Here is more details how could this works:

Following syntax

Public Property Name As String => {Expression}

would be expanded to get and optinaly set depending if it's possible to construct assigment for given expression. When ReadOnly is specified then setter will not be generated. In case that it's not possible to construct assigment to the expression e.g. constant value, then property needs to be ReadOnly.

(Instead of => maybe it could be used just = sign.)

Public Property Name As String  => {Expression}
    Get
        Return {Expression}
    End Get
    Set(value As String)
        {Expression} = value
    End Set
End Property

' In case of throw expression
Public Property Name As String => {ThrowExpression}
    Get
        {ThrowExpression}
    End Get
    Set(value As String)
        {ThrowExpression}
    End Set
End Property

Here are some more examples how it could be used.

Class Animal Public Property ID As Integer Public Property Name As String End Class

Class CustomerView Private m_Customer As Customer Private m_Animal As Animal

Public ReadOnly Property CustomerID As Integer => m_Customer.ID
Public ReadOnly Property AnimalID As Integer => m_Animal.ID
Public Property CustomerName As String => m_Customer.Name
Public Property AnimalName As String => m_Animal.Name   

End Class


An extension to this would be to add support for chaining properties, but generic indexed properties and byref parameters are not currently supported so i guess it would be lot's of work.

- Custom property indexer is used to implement common logic for multiple properties

```vb
Class Customer
    Private m_ID As Integer
    Private m_FirstName As String
    Private m_LastName As String

    Public ReadOnly Property Id As Integer => CustomIndexer(m_ID)
    Public Property FirstName As String => CustomIndexer(m_FirstName)
    Public Property LastName  As String => CustomIndexer(m_LastName)
    ' More properties

    Public Property IsChanged As Boolean

    Private Property CustomIndexer(Of T) (ByRef field As T)  As T
        Get
            return field
        End Get
        Set(value As T)
            field = value 
            IsChanged = True
        End Set
    End Property 
End Class
Class Customer 
    Public Property ID As Integer
    Public Property FirstName As String
    Public Property LastName As String
    ...
End Class

Class CustomerProxy
    Private m_Customer As Customer

    Public Property FirstName As String => PropIndexer(m_Customer.FirstName)
    Public Property LastName As String => PropIndexer(m_Customer.LastName)

    Private Property PropIndexer(Of T) (prop As PropertyExpression)  As T
        Get
            Return CType(prop.GetValue() , T)
        End Get
        Set(value As T)
            prop.SetValue(value)
        End Set
    End Property 
End Class
bandleader commented 6 years ago

Highly recommend using => or similar (or even Get =>, or Get =) as opposed to just Get (or With): (reasons and examples below)

  1. Readability: Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException() looks like just a long string of keywords; it isn't obvious where the definition expression begins.
  2. This is not just for properties, it's for methods as well. C# added more member types in C# 7: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members
  3. If you are worried that => is too C#-like, feel free to use a simple =. That's how it's done in Scala for instance: def myMethod(x:Int):Int = 5 + x For properties you would need to add Get (as I wrote above) because otherwise it's simply an initialized shorthand property. Public Property myProp As Integer = 5 (This doesn't apply to Scala. as setters are a different ballgame there.)

Possible syntax options

Public Property myProp As Integer Get = 5 Public Property myProp As Integer Get => 5 Public Property myProp As Integer => 5 Public Function myMethod() As Integer => 5

I cannot say enough how much this feature would mean to me, in expressing 'computed properties' / expressions concisely in data classes -- makes a big difference as far as being able to split them up and compose them together. Hoping we can get a timeline on this from the VB team.

(In general features that are already in C# are a good candidate for inclusion in VB, barring reasons specifically not to, correct? And it's just a question of resources?)

rskar-git commented 6 years ago

I have a feeling that => is going to be the winning symbol, especially as we explore in what ways in can be employed in parallel with how it's used in C#.

So, if I'm understanding things right, then:

ReadOnly Property myProp As T => (any expression which returns a value; or Throw exception)

WriteOnly Property myProp As T => (any statement or function to make assignment via parameter Value; or Throw exception)

Property myProp As T => (something assignable, such as field or property, and must also be read-able; or Throw exception)

What if hooks to events or interfaces necessary? Perhaps do Handles and Implements before =>.

Property myProp As T Implements IFoo.FooProp => myStruct.myStructField

Sub Bar() Handles Foo.Boom => Throw New Exception("Boom!")

Add to that as many adaptations of C# usage of => as possible.

Anyway, below is hypothetical VB syntax to consider.

' Adaptations of examples from:
'    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members

' ==== Methods
Imports System

Public Class Person

    Public Sub New Person(firstName As String, lastName As String)
        fname = firstName
        lname = lastName
    End Sub

    Private fname As String
    Private lname As String

    Public Function ToString() As String => $"{fname} {lname}".Trim()

    Public Sub DisplayName() => Console.WriteLine(ToString())

End Class

Class Example
    Shared Sub Main()
        Dim p As New Person("Mandy", "Dejesus")
        Console.WriteLine(p)
        p.DisplayName()
    End Sub
End Class

'==== Constructors, and
'==== Property get statements, and
'==== Property set statements
Public Class Location

    Private locationName As String

   ' This one may be tricky - include auto-generated call to MyBase.New()?
    Public Sub New(name As String) => locationName = name

    Public Property Name
        Get => locationName
        Set => locationName = Value ' sorry about that capital V, @AnthonyDGreen
    End Property

    ' These are reiterations of what @AdamSpeight2008 is proposing
    ' (my understanding of it, anyway)
    Private fooeyText As String
    Public WriteOnly Property FooeyNess As String => fooeyText = Value
    Public ReadOnly Property Fooey As String => $"I say 'Fooey!' to that silly {fooeyText}."

    ' This is a reiteration of what @edin is proposing (my understanding of it, 
    ' anyway), where the expression must be an 'l-value' (something assignable, 
    ' such as a field or property) - hence must be read-able and write-able.
    Private m_DataRow As DataRow
    Public Property Id        As Id     => DataRow("col_ID")
    Public Property FirstName As String => DataRow("col_FirstName")
    Public Property LastName  As String => DataRow("col_LastName")

    Private Property DataRow(Of T) (name as String)  As T
        Get => CType(m_DataRow(name), T)
        Set => m_DataRow(name) = Value
    End Property 

End Class

'==== Finalizers
Imports System

Public Class Destroyer

    Public Overrides Function ToString() As String => GetType().Name

   ' This one may be tricky - include auto-generated call to MyBase.Finalize()?
    Protected Overrides Sub Finalize() => Console.WriteLine($"The {ToString()} destructor is executing.")

End Class

'==== Indexers
Imports System
Imports System.Collections.Generic

Public Class Sports
    Dim types As String() = { "Baseball", "Basketball", "Football", 
                              "Hockey", "Soccer", "Tennis", 
                              "Volleyball" }

    Default Public Property Item(i As Integer) As String
        Get => types(i)
        Set => types(i) = Value
    End Property
End Class
rskar-git commented 6 years ago

I just thought of this, the following:

'==== Indexers
Imports System
Imports System.Collections.Generic

Public Class Sports
    Dim types As String() = { "Baseball", "Basketball", "Football", 
                              "Hockey", "Soccer", "Tennis", 
                              "Volleyball" }

    Default Public Property Item(i As Integer) As String
        Get => types(i)
        Set => types(i) = Value
    End Property
End Class

Could be done like this (idea from @edin):

'==== Indexers
Imports System
Imports System.Collections.Generic

Public Class Sports
    Dim types As String() = { "Baseball", "Basketball", "Football", 
                              "Hockey", "Soccer", "Tennis", 
                              "Volleyball" }

    Default Public Property Item(i As Integer) As String => types(i)
End Class
gilfusion commented 6 years ago

How about a keyword like Returns or Gets? It would match up with Handles and Implements, which also appear in that position.

Function Abs(i As Integer) As Integer Returns If(i < 0, -i, i)
ReadOnly Property Foo As Double Gets _foo
Property FooString As String Gets CStr(_foo) Sets _foo = Val(value)
bandleader commented 6 years ago

@gilfusion How about a keyword like Returns or Gets? It would match up with Handles and Implements, which also appear in that position.

  • [plus] Good in that it is more BASIC-like
  • But I don't think it delineates (as clearly as a symbol like = or =>) where the method body (expression) begins. It looks like Returns If(... is just additional keywords tacked onto the end of the property.
  • Also introduces complexity with the 3 different keywords: Returns, Gets, Sets.
  • And it is not clear what to do for a Sub. Does?
bandleader commented 6 years ago

Regarding mapped properties (Get/Set in one line)

I see that a lot of people (@edin, @rskar-git) have expressed interest in doing this for Property Get/Set in one line -- i.e. Property Foo As String => items("foo") creates both a getter with the return expression items("foo") AND a setter items("foo") = value.

While I appreciate the utility of the idea for concisely defining what could be called a "mapping," I must say that

Either way, it is critical that this not be the only way to do expression-bodied properties; we have to allow for Get/Set separately as well, and also for ReadOnly Propertys in one line.

rskar-git commented 6 years ago

@bandleader @AdamSpeight2008 gave a thumbs-up to the post @edlin made on Apr 22, 2017, which elaborates on this "mapped properties" concept. All I did was paraphrase that, and then attempted to make some other hypothetical syntax (using C# examples as a go-by). I'm just trying to follow along. In the meantime, I believe AdamSpeight2008 is working on the prototype, and I don't know if he's looking into "mapped properties" or not.

You may be right that something like:

Public Property Item(i As Integer) As String => types(i)

may be something rather complex to implement, but wow! it's so handy it would certainly be worth trying.

AdamSpeight2008 commented 6 years ago

@rskar-git I'm not currently working a prototype for this, wanting try and finish up some of the others first.

bandleader commented 6 years ago

OK, so in summary for the VB team to review: (@KathleenDollard et al) (Did my best to capture the ideas above: the various member types, our preference toward =>, and the idea of mapped properties)

'=== Subs/Functions:
Function AddTwo(i As Integer) As Integer => i + 2

Sub PrintInt(i As Integer) => Debug.Print(i)

'=== Property get/set:
Property MyProp(i As Integer) As Integer
    Get => i + 2
    Set => Debug.Print("Done; thank you!")
End Property

'=== Read-only property -- only one line necessary:
ReadOnly Property MyProp(i As Integer) As Integer => i + 2

Things to be done later if at all

Comments/feedback welcome from both community and VB team.

pricerc commented 6 years ago

VB has many strengths vs the C-esque languages, a key one is that it doesn't aim for terse for the sake of being terse. I like this because terseness doesn't help the maintainer of the code who comes along in two years time and has to de-terse the code before they can debug it.

As part of that, I don't see any particular need to seek 'feature parity' with C# for its own sake, especially when it's for things that don't radically improve the language.

For example, I've never been a fan of anonymous methods, because when I'm writing code, I'm always thinking about the fact that I'm going to have to debug and tune it, and like to be able to add instrumentation code to methods, and to be able to see all my methods in a stack trace. And about 1/2 the time I've used anonymous methods, I've ended up refactoring them into actual methods anyway.

So while I get the goals in this thread, adding anonymous property getters using what looks like a C# lambda construct just feels wrong.

I realize that 'legibility' is subjective, but it's also contextual - what I find legible in C# code, I don't necessarily find legible in VB, and vice-versa. E.g. in this case, I don't find this:

 Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()

more legible than the 'traditional'

    Public Overrides ReadOnly Property Value As Integer
        Get
            Throw New NotImplementedException()
        End Get
    End Property

In fact, I find it quite the opposite. Possibly just because the latter is a pattern I've been looking at for the last 15 years, but I still think it presents better. And as someone has mentioned somewhere on this forum, would be easier for a non-programmer looking over a shoulder to understand.

I think that declarative stuff, like the @AnthonyGreen's stuff in #282 is ok, because it's about reducing repetitive boiler-plate code, and I can see how it will reduce errors, whereas I see these sneaky beggars as being more likely to increase them

bandleader commented 6 years ago

@pricerc I can't see at all how you think the traditional way is more readable than the expression-bodied way. Nor how a non-programmer could understand it better.

Public Property x As Integer => someValue is just like regular declarations: Dim x As Integer = someValue I can't understand why you feel multi-line boilerplate is desirable here -- when the entire body is just a single-line expression!

gilfusion commented 6 years ago
ReadOnly Property ThisTime As Date = DateTime.Now
ReadOnly Property ThatTime As Date => DateTime.Now

Maybe this isn't an issue in languages where this syntax currently being used, but I can see there being some confusion between those two lines of code, which do quite different things.

pricerc commented 6 years ago

@bandleader, as I said, style preferences are subjective, a personal preference.

So yes, surprising though it may be, I do prefer multi-line boiler plate for a method definition - I've been troubled by enough one-line anonymous methods whilst debugging, that I now avoid them.

And yes, I have refactored more than one single-line lambda into a discrete method - usually while debugging a performance problem, and in both C# and VB.

I'd also argue there is a subtle but important difference between 1) a property with a backing store and initial value and 2) a property with a backing method

The former I expect to happen once as part of class instantiation, and be optimized into the class constructor, whereas the latter is effectively a method declaration. For me (and I'm sure at least some others), the multi-line declaration is a pattern I recognise when scanning for methods, which are different from assignments.

My main concern is about maintenance. I don't know about you, but I sometimes have to maintain code that I haven't touched in several years, and that process often starts with scanning over the code; for me, being able to easily differentiate between methods and assignments is helpful in rebuilding an understanding of what it does. Currently, this is easy, as there is a clear distinction. But as mentioned by @gilfusion having to differentiate between these two would slow that process for me:

ReadOnly Property ThisTime As Date = DateTime.Now
ReadOnly Property ThatTime As Date => DateTime.Now
ghost commented 5 years ago

There is a suggestion in C# to use <=> and I think it is more clear than => and gives the meaning of mapping:

Property ThisTime As Date = DateTime.Now ' initial value
Property ThatTime As Date <=> mydate ' map mydate to set and get 
ghost commented 5 years ago

@AdamSpeight2008 @reduckted @AnthonyDGreen @scottdorman @xieguigang @KathleenDollard Another way:

Property ThatTime As Date Using mydate

I like this one. Using is reserved and gives a clearer meaning the the property is relying on the var or expression. It allows also to do this:

Property ThatTime As Date Using mydate = Date.Now

Which is equivalent to:

Dim mydate = Date.Now
Property ThatTime As Date 
    Get()
       return mydate 
    End get
    Set (Value as Date)
       mydate = Value
    End Set
End Property
ghost commented 5 years ago

This priposal is here for one yaer, and tey no decision made?

pricerc commented 5 years ago

There is a suggestion in C# to use <=> and I think it is more clear than => and gives the meaning of mapping:

Property ThatTime As Date <=> mydate ' map mydate to set and get 

I don't understand how this could ever be useful.

If mydate is private and ThatTime is just the public interface to mydate, then you may as well just get rid of mydate completely, and use ThatTime.

The exception would be if you had an existing public mydate, and you want to introduce a synonym for it called ThatTime. In that case, I'd rather have a new keyword to deal with that, like Synonym <SynonymName> = <SourceName>, than yet another symbol of dubious origin.

pricerc commented 5 years ago

403 resurrected this concept, but along the way in there, a slightly alternative form for a 'lambda-ish' readonly form spring to mind. Rather than using =>, which I don't think is very 'VB', how about:


' instead of
Friend ReadOnly Property Kind As SyntaxKind => Return CType(Me.RawKind, SyntaxKind)

' a more VB style
Friend ReadOnly Property Kind As SyntaxKind = Get CType(Me.RawKind, SyntaxKind)

The Parser would process = Get in the same way that @AdamSpeight2008 proposed it deal with => Return. Except with less typing, and code that looks (to me) more like VB.

craigajohnson commented 5 years ago

The overall goal as I interpret it is to enjoy a convenient (and highly readable) single-line syntax similar to C#. I admit it would seem a little odd to only have access to => specifically for expression-bodied members and not as a general shortcut syntax.

Would there be any ambiguity or limitation with just using Return after the = sign to denote an expression instead of an initialized value (certainly possible this is a redundant suggestion):

Public Readonly Property IsGrover as Boolean = Return CheckStuffOut()

pricerc commented 5 years ago

The overall goal as I interpret it is to enjoy a convenient (and highly readable) single-line syntax similar to C#. I admit it would seem a little odd to only have access to => specifically for expression-bodied members and not as a general shortcut syntax.

Would there be any ambiguity or limitation with just using Return after the = sign to denote an expression instead of an initialized value (certainly possible this is a redundant suggestion):

Public Readonly Property IsGrover as Boolean = Return CheckStuffOut()

I guess that's a subjective style choice. Either would work, but here's my thinking:

400 suggested an alternative handling of iterators (basically replacing Function with Iterator) that I quite like the idea of, which (I think) is why I thought of having a Get (or Set, for that matter) as Property appropriate.

More completely, it goes a bit like:

dovisutu commented 4 years ago

Maybe if somebody wants to use the default property in order to avoid Diming a private member but wants to add some check when setting, we can use a syntax like this:

' *partial*-default property
Public Property Content As List(Of String)
    Get With Default
    Set(Value As List(Of String))
        If Value Is Nothing Then Throw New NullReferenceException()
        Content = Value ' Access with property name
    End Set
End Property

where getter equals to this:

    Get
        Return Content ' or equivalent inner private variable
    End Get

We can even go further with setter(currently adds a kinda-frequently-used keyword Checks, can be changed/use exist keywords):

    Set(Value As List(Of String)) _ 
        Checks Value IsNot Nothing Else Throw New NullReferenceException() _
        With Default