-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Conversion expression rewrite #21040
Conversation
else | ||
{ | ||
throw new InvalidCastException(string.Format(CSharpResources.IConversionExpression_Is_Not_A_Valid_CSharp_Conversion, conversionExpression)); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Done with first pass. |
/// Otherwise, returns null. | ||
/// </summary> | ||
IMethodSymbol MethodSymbol { get; } | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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#?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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#.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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")}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
…donly, make FormatBool static.
@@ -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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is HasFlag
defined?
There was a problem hiding this comment.
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.
LGTM |
Ping @dotnet/roslyn-compiler @dotnet/analyzer-ioperation for a second review from both please. |
…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 ...
…sion-expression-rewrite * dotnet/features/ioperation: Tagging VB/C# Ioperation tests (dotnet#21310)
I don't believe so, we should be able to remove it. |
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:
as
and VBTryCast
distiction.IConversionExpression
that returns theIConversion
. This will result in a box, which is unfortunate, but the resulting object is castable directly to either C#'s or VB'sConversion
.IConversionExpression
that allow the user to directly retrieve the underlyingConversion
. These are direct access, and require no boxing.IsReference
toIConversion
.Please leave comments and criticisms on the initial implementation.