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

Conversion expression rewrite #21040

Merged

Conversation

333fred
Copy link
Member

@333fred 333fred commented Jul 21, 2017

This is the interface changes required to implement #20046. This is just the changes to CodeAnalysis, CSharpCodeAnalysis, and VisualBasic. I'd like to get comments from @dotnet/analyzer-ioperation @CyrusNajmabadi @cston for this initial implementation. Changes from the initial proposal are as follows:

  • Added ThrowsExceptionOnFailure for C# as and VB TryCast distiction.
  • Removed the generics. They felt really bad. They've been replaced with
    • A property on IConversionExpression that returns the IConversion. This will result in a box, which is unfortunate, but the resulting object is castable directly to either C#'s or VB's Conversion.
    • Extension methods for VB and C# on IConversionExpression that allow the user to directly retrieve the underlying Conversion. These are direct access, and require no boxing.
  • Added IsReference to IConversion.

Please leave comments and criticisms on the initial implementation.

@333fred 333fred added Area-Analyzers PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. labels Jul 21, 2017
@333fred 333fred requested review from CyrusNajmabadi, cston and a team July 21, 2017 21:49
else
{
throw new InvalidCastException(string.Format(CSharpResources.IConversionExpression_Is_Not_A_Valid_CSharp_Conversion, conversionExpression));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just return ((BaseCSharpConversionExpression)conversionExpression).ConversionInternal; since that will also throw an InvalidCastException on failure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think the explicit error message here will provide a better use experience.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the exception should probably be something other than InvalidCastException since casting seems like an implementation detail. Perhaps ArgumentException.

@@ -5108,4 +5108,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_InvalidDebugInfo" xml:space="preserve">
<value>Unable to read debug information of method '{0}' (token 0x{1:X8}) from assembly '{2}'</value>
</data>
<data name="IConversionExpression_Is_Not_A_Valid_CSharp_Conversion" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IConversionExpression_0_is_not_a_valid_CSharp_conversion_expression or IConversionExpression_0_is_not_a_valid_CSharp_conversion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other exception message ids avoid underscores. Please use IConversionExpressionIsNotAValidCSharpConversion or something shorter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is in the compilers project and not IDE, I went with Compilers-style.

@@ -5108,4 +5108,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_InvalidDebugInfo" xml:space="preserve">
<value>Unable to read debug information of method '{0}' (token 0x{1:X8}) from assembly '{2}'</value>
</data>
<data name="IConversionExpression_Is_Not_A_Valid_CSharp_Conversion" xml:space="preserve">
<value>IConversionExpression '{0}' is not a valid C# conversion expression.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No period at the end.

/// <summary>
/// True if and only if the operation is performed by an operator method.
/// </summary>
public bool UsesOperatorMethod { get; }
public bool UsesOperatorMethod => Conversion.IsUserDefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid boxing, this should be an abstract property, implemented by the derived types. Same for OperatorMethod.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is a teaching moment. I would not have expected any boxing to occur here. Why would it, since everything in this chain is bool?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, IMethodSymbol will already be boxed, so I don't see any value in duplicating the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is a teaching moment. I would not have expected any boxing to occur here. Why would it, since everything in this chain is bool?

If "Conversion" returns an IConversion, then this will box (it's an interface, they're always on the heap). If you can access something of type TConversion, then it will be a constrained call through that interface property and it will not box if the TConversion is actaully instantiated at runtime to be a struct type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but we're not talking about conversion in this comment, right? I'm seeing this on public bool UsesOperatorMethod => Conversion.IsUserDefined;, and I'm not understanding how this can box.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, wait, I understand. Another argument for going with the common Conversion struct, I guess.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does your "Conversion" property return?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommonConversion?

@@ -1 +1,3 @@
Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_2 = 702 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion
Microsoft.CodeAnalysis.CSharp.IConversionExpressionExtensions
static Microsoft.CodeAnalysis.CSharp.IConversionExpressionExtensions.GetCSharpConversion(this Microsoft.CodeAnalysis.Semantics.IConversionExpression conversionExpression) -> Microsoft.CodeAnalysis.CSharp.Conversion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name would suffice as GetConversion. We don't have ClassifyCSharpConversion and ClassifyVisualBasicConversion. We just ahve the extension ClassifyConversion at the particular C# and VB layers.

/// Value to be converted.
/// </summary>
public abstract IOperation Operand { get; }
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of cref, you can just do see="T:fullname"

/// <summary>
/// True if and only if the conversion is indicated explicity by a cast operation in the source code.
/// </summary>
public bool IsExplicit { get; }
public bool IsExplicitInCode { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this redundant with an existing boolean on IOperation? i.e. don't we have an IsImplicit boolean on IOperation we can use for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we do currently. We do have #10214 tracking something for exposing whether a node was compiler generated. @cston I think that would be enough to cover this case, can you think of any exceptions I'm missing?

@@ -1294,36 +1294,59 @@ public LazyConditionalChoiceExpression(Lazy<IOperation> condition, Lazy<IOperati
/// <summary>
/// Represents a conversion operation.
/// </summary>
internal abstract partial class BaseConversionExpression : Operation, IHasOperatorMethodExpression, IConversionExpression
internal abstract partial class BaseConversionExpression<TConversion> : Operation, IHasOperatorMethodExpression, IConversionExpression
where TConversion : struct, IConversion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this base class providing value or could the language-specific derived types simply implement the various interfaces?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It provides the visitor implementation that both languages use, plus any common properties that don't need to be reimplemented. I'd rather leave it.

/// <summary>
/// True if the conversion will fail with an exception at runtime if the cast fails. This is false for C#'s <code>as</code>
/// operator and for VB's <code>TryCast</code> operator.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify that this is also false for things like an (int) cast on a long in unchecked code, but true in checked code.

Copy link
Member Author

@333fred 333fred Jul 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we need a different wording here, maybe will throw a <see cref="InvalidCastException" /> at runtime? Checked vs unchecked is tracked by #18055, and I'm going to work a separate IsChecked boolean into this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since checked and unchecked are a different set of exceptions.

}

public override string LanguageName => LanguageNames.CSharp;
// We override this here so that we don't return the method symbol if the internal conversion is a MethodGroup conversion, instead of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not push this logic down?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing something, but what benefit would we get by forcing both implementing classes to duplicate this functionality?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, my down is synonymous with "up" :) Push this into the most base conversion expressoin type.

End If
End Function
End Module

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove newline

@@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.TypeSymbolExtensions
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
Imports Microsoft.CodeAnalysis.Semantics
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort

@@ -5500,5 +5500,7 @@
<data name="ERR_InvalidDebugInfo" xml:space="preserve">
<value>Unable to read debug information of method '{0}' (token 0x{1}) from assembly '{2}'</value>
</data>
</root>

<data name="IConversionExpression_Is_Not_Visual_Basic_Conversion" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match key to value.

''' <exception cref="InvalidCastException">If the <see cref="IConversionExpression"/> was not created from Visual Basic code.</exception>
<Extension>
Public Function GetVisualBasicConversion(conversionExpression As IConversionExpression) As Conversion
If TypeOf conversionExpression Is BaseVisualBasicConversionExpression Then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can just do a TryCast to avoid the two typechecks.

@CyrusNajmabadi
Copy link
Member

Done with first pass.

/// Otherwise, returns null.
/// </summary>
IMethodSymbol MethodSymbol { get; }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an advantage to having this as an interface rather than a struct? A struct would be lightweight (a set of bits and an IMethodSymbol) and could be constructed from a Conversion easily.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be a good idea. While there is a nicety to being able to cast from the IConversion to the Conversion struct, that doesn't seem necessary given that one can use the language specific GetConversion methods. Since any casting from IConversion to Conversion would have to happen at a language specific level, it would also just be possible to call GetConversion at that point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I can look at changing this to a struct. @cston or @CyrusNajmabadi, any ideas for what this should be named, since we already have Conversion structs for VB and C#?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing with the struct approach is that we're going to have provide a similar GetConversion operation for the In/Out conversion scenario we discussed previously.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible option would be to have IConversionExpression and IConversionExpression<TConversion>. Clients could cast the operation they had to somethin glike IConversionExpression<Conversion> to then get a strongly typed, non-boxing version of .Conversion.

Same would exist for the in/out conversion issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, from a user perspective, the extension method version is better. I'd prefer not to have direct casts in end user code if we can help it. I don't really see a problem with having the extensions I mentioned, I was just pointing it out.

@@ -904,6 +904,17 @@ public override int GetHashCode()
return !(left == right);
}

public static implicit operator CommonConversion(Conversion csharpConversion)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the implicit operator necessary? Consider making this an explicit method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

IsReference = isReference;
IsUserDefined = isUserDefined;
MethodSymbol = methodSymbol;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool fields could be combined into a single int.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -284,23 +284,23 @@ Namespace Microsoft.CodeAnalysis.Semantics
End Function

Private Function CreateBoundMeReferenceOperation(boundMeReference As BoundMeReference) As IInstanceReferenceExpression
Dim instanceReferenceKind As InstanceReferenceKind = If(boundMeReference.WasCompilerGenerated, InstanceReferenceKind.Implicit, InstanceReferenceKind.Explicit)
Dim instanceReferenceKind As InstanceReferenceKind = If(boundMeReference.WasCompilerGenerated, instanceReferenceKind.Implicit, instanceReferenceKind.Explicit)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary change to InstanceReferenceKind here and below.

Copy link
Member Author

@333fred 333fred Aug 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, formatter must changed that, I didn't explicitly change it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Friend Class VisualBasicOperationCloner
Inherits OperationCloner

Public Shared Property Instance As OperationCloner = New VisualBasicOperationCloner()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadOnly Property, or perhaps a ReadOnly field here and in C#.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I went with ReadOnly Property.

@@ -791,7 +791,7 @@ Namespace Microsoft.CodeAnalysis.Semantics
Dim clauseValue = GetSingleValueCaseClauseValue(boundSimpleCaseClause)
Dim value As Lazy(Of IOperation) = New Lazy(Of IOperation)(Function() Create(clauseValue))
Dim equality As BinaryOperationKind = GetSingleValueCaseClauseEquality(clauseValue)
Dim caseKind As CaseKind = CaseKind.SingleValue
Dim caseKind As CaseKind = caseKind.SingleValue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert changes to CaseKind and LoopKind.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -194,6 +207,8 @@ private void LogType(ITypeSymbol type, string header = "Type")
LogString($"{header}: {typeStr}");
}

private string FormatBoolProperty(string propertyName, bool value) => $"{propertyName}: {(value ? "True" : "False")}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -97,6 +97,11 @@ internal IOperation GetOperationInternal(SyntaxNode node, CancellationToken canc
protected abstract IOperation GetOperationCore(SyntaxNode node, CancellationToken cancellationToken);

/// <summary>
/// Deep Clone given IOperation
/// </summary>
internal abstract T CloneOperation<T>(T operation) where T : IOperation;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid a generic virtual method, and generic methods in several classes, by introducing an abstract CloneOperationCore method?

internal abstract IOperation CloneOperationCore(IOperation op);

internal T CloneOperation<T>(T op) where T : IOperation => (T)CloneOperationCore(op);

/// The existence of a conversion does not necessarily imply that the conversion is valid.
/// For example, an ambiguous user-defined conversion may exist but may not be valid.
/// </remarks>
public bool Exists => _conversionKind.HasFlag(ConversionKind.Exists);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is HasFlag defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's from System.Enum.

@cston
Copy link
Member

cston commented Aug 5, 2017

LGTM

@333fred
Copy link
Member Author

333fred commented Aug 7, 2017

Ping @dotnet/roslyn-compiler @dotnet/analyzer-ioperation for a second review from both please.

333fred added 2 commits August 7, 2017 13:26
…sion-expression-rewrite

* dotnet/features/ioperation:
  fix file path casing for analyzer packate
  Use 15.3 Preview 7 for VSI CI machines
…sion-expression-rewrite

* dotnet/features/ioperation: (31 commits)
  Fix lambda EnC (dotnet#21291)
  Use 15.3 Preview 7 for VSI CI machines
  Add cancellation between each document.
  Remove unused private method (dotnet#21268)
  Work around SDK 1.1 issue around netcoreapp2.0
  Update Language Feature Status.md
  Const warnings shouldn't cause emit error (dotnet#20729)
  Fix the symbol test
  Choose test config based on TargetFramework
  Standardize on the portable ThrowingTraceListener
  Enforce using UnitTestPortable
  Correct the order of parameters to Version
  Clean up RunCsc/Vbc.cmd a tiny bit
  Remove usages of 'Foo' from our codebase. (dotnet#21224)
  Make RunCsc/Vbc scripts respect DOTNET_HOST_PATH
  Fix typo
  ignoring exceptions about not running on main thread
  Updating VisualStudioSymbolSearchProgressService to consume new API
  Updating visual studio references
  Accept pre-rel version number
  ...
333fred added 2 commits August 8, 2017 10:42
…sion-expression-rewrite

* dotnet/features/ioperation:
  Tagging VB/C# Ioperation tests (dotnet#21310)
@333fred 333fred merged commit 6693d8a into dotnet:features/ioperation Aug 8, 2017
@333fred 333fred deleted the conversion-expression-rewrite branch August 8, 2017 20:29
@genlu
Copy link
Member

genlu commented Aug 12, 2017

        ConversionKind conversionKind = GetConversionKind(boundConversion.ConversionKind);

Is ConversionKind still used anywhere? Can we remove it now?


Refers to: src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs:629 in f2d8739. [](commit_id = f2d8739, deletion_comment = False)

@333fred
Copy link
Member Author

333fred commented Aug 12, 2017

I don't believe so, we should be able to remove it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants