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
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc55977
Adds initial IConverisonExpression API rewrite.
333fred Jul 21, 2017
f2c328d
Updated documentation, added ThrowsExceptionOnFailure field
333fred Jul 21, 2017
0158878
Moved fromm an interface IConversion to a CommonConversion struct.
333fred Jul 26, 2017
c850505
Added IsChecked bool property
333fred Jul 26, 2017
9815e73
Use TryCast for cleaner code
333fred Jul 26, 2017
eb9fdb7
Remove period from error message
333fred Jul 26, 2017
fcae24e
Addressed minor feedback items.
333fred Jul 27, 2017
ac70ad0
Removed virtual from MethodName property, made Conversion a synthesiz…
333fred Aug 1, 2017
2be4107
Move Language to IOperation root.
333fred Aug 1, 2017
62c84ab
Move ThrowsExceptionOnFailure to IsTryCast, invert logic for this.
333fred Aug 1, 2017
1094d2b
Updated test infrastructure code.
333fred Aug 2, 2017
08b5bc3
Updated expected tests output for all tests involving IConversionExpr…
333fred Aug 4, 2017
28ac13f
Merge remote-tracking branch 'dotnet/features/ioperation' into conver…
333fred Aug 4, 2017
90d781e
Moved OperationCloner to semantic model, fixed non-Compilers.sln comp…
333fred Aug 4, 2017
4d1573a
Revert resources change
333fred Aug 4, 2017
f00bd83
More test updates. Added checked tests and fixed checked bug.
333fred Aug 4, 2017
34a897e
Moved GetCSharp/VBConversion to GetConversion, into CSharp/VBExtensio…
333fred Aug 4, 2017
c1b0ca3
Removed LanguageName from ConversionExpression
333fred Aug 4, 2017
aed4959
Revert case changes in VBOperationFactory, make VBOperationCloner Rea…
333fred Aug 4, 2017
9c0a391
Move CloneOperation to not be generic calls all the way down.
333fred Aug 4, 2017
65f5f24
Removed boxing HasFlag usage from CommonConversion.
333fred Aug 5, 2017
5d2ffcb
Fixed IConversionExpression comment.
333fred Aug 5, 2017
ee30c32
Fixed failing unit tests, sorted and removed unnecessary usings in Co…
333fred Aug 6, 2017
8a61410
Merge remote-tracking branch 'dotnet/features/ioperation' into conver…
333fred Aug 7, 2017
901b072
Merge remote-tracking branch 'dotnet/features/ioperation' into conver…
333fred Aug 7, 2017
d4d8834
Merge remote-tracking branch 'dotnet/features/ioperation' into conver…
333fred Aug 8, 2017
f2d8739
Tagged new unit tests with IOperation tag.
333fred Aug 8, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;

namespace Microsoft.CodeAnalysis.CSharp
{
Expand Down Expand Up @@ -903,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

{
var methodSymbol = csharpConversion.IsUserDefined ? csharpConversion.MethodSymbol : null;
return new CommonConversion(csharpConversion.Exists,
csharpConversion.IsIdentity,
csharpConversion.IsNumeric,
csharpConversion.IsReference,
csharpConversion.IsUserDefined,
methodSymbol);
}

#if DEBUG
internal string Dump()
{
Expand Down
9 changes: 9 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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="IConversionExpressionIsNotCSharpConversion" xml:space="preserve">
<value>{0} '{1}' is not a valid C# conversion expression</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.CodeAnalysis.Semantics;

namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract class BaseCSharpConversionExpression : BaseConversionExpression
{
protected BaseCSharpConversionExpression(Conversion conversion, bool isExplicitInCode, bool throwsExceptionOnFailure, bool isChecked, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
base(conversion, isExplicitInCode, throwsExceptionOnFailure, isChecked, syntax, type, constantValue)
{
ConversionInternal = conversion;
}

internal Conversion ConversionInternal { get; }
Copy link
Member Author

Choose a reason for hiding this comment

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

@cston style question: is the preferred strategy to have both the CommonConversion and Conversion struct around, or to perform the conversion when requested? ie public CommonConversion Conversion => (CommonConversion)ConversionInternal;

Copy link
Member

Choose a reason for hiding this comment

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

If creating the struct is sufficiently lightweight, then it's reasonable to create it when requested. But either way, please use an explicit method rather than a conversion operator.

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 left it as is for now. If you'd prefer to have it created on demand, let me know and I'll change it.


public override string LanguageName => LanguageNames.CSharp;
Copy link
Member

Choose a reason for hiding this comment

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

  1. should be Language not LanguageName.
  2. I don't think we need this. or we should move this to the root IOperation. It would fit there given that we have ISymbol.Language and SyntaxNode.Language. If you want we can ratify this at the design meeting tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. I won't be here tomorrow, @jinujoseph can you add this to the list?

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'm of the opinion that we need it somewhere. Otherwise, the expected path for me to use the API is to call one of our GetConversion methods, catch the resulting ArgumentException if the source code wasn't that one, and then try the other one. Our users need something to switch off of.

Copy link
Member Author

Choose a reason for hiding this comment

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

@CyrusNajmabadi going off @cston's moving things to CommonConversion suggestion above, what about moving it to the CommonConversion struct?

Copy link
Member

Choose a reason for hiding this comment

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

We could certainly have it there (though it would add space to every conversion struct (unless you encode it as a bit)). HOwever, i think its still sensible for it to just be on IOperation like it is on ISymbol and SyntaxNode.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, the Language property should be on IOperation.

}

internal sealed partial class CSharpConversionExpression : BaseCSharpConversionExpression
{
public CSharpConversionExpression(IOperation operand, Conversion conversion, bool isExplicit, bool throwsExceptionOnFailure, bool isChecked, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
base(conversion, isExplicit, throwsExceptionOnFailure, isChecked, syntax, type, constantValue)
{
Operand = operand;
}

public override IOperation Operand { get; }
}

internal sealed partial class LazyCSharpConversionExpression : BaseCSharpConversionExpression
{
private readonly Lazy<IOperation> _operand;
public LazyCSharpConversionExpression(Lazy<IOperation> operand, Conversion conversion, bool isExplicit, bool throwsExceptionOnFailure, bool isChecked, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
base(conversion, isExplicit, throwsExceptionOnFailure, isChecked, syntax, type, constantValue)
{
_operand = operand;
}

public override IOperation Operand => _operand.Value;
}

public static class IConversionExpressionExtensions
{
/// <summary>
/// Gets the underlying <see cref="Conversion"/> information from this <see cref="IConversionExpression"/>. This
/// <see cref="IConversionExpression"/> must have been created from CSharp code.
/// </summary>
/// <param name="conversionExpression">The conversion expression to get original info from.</param>
/// <returns>The underlying <see cref="Conversion"/>.</returns>
/// <exception cref="InvalidCastException">If the <see cref="IConversionExpression"/> was not created from CSharp code.</exception>
public static Conversion GetConversion(this IConversionExpression conversionExpression)
{
if (conversionExpression is BaseCSharpConversionExpression csharpConversionExpression)
{
return csharpConversionExpression.ConversionInternal;
}
else
{
throw new ArgumentException(string.Format(CSharpResources.IConversionExpressionIsNotCSharpConversion,
nameof(IConversionExpression),
conversionExpression));
Copy link
Member

Choose a reason for hiding this comment

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

Are we relying on conversionExpression.ToString() for the exception message? Is there a reasonable string representation in all cases?

Are we testing the ArgumentException case?

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 haven't updated the tests at all for the new conversion scheme, so this method hasn't been tested at all yet. We are currently relying on ToString, and there probably isn't a reasonable string representation currently. Is our convention to include the object in the error message, or to not include it and just say IConversionExpression is not a C#/VB conversion?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps omit the object entirely.

Copy link
Member

Choose a reason for hiding this comment

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

Please use the ArgumentException..ctor overload that takes a message and argument name.

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.

}
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.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -581,28 +581,28 @@ private IOperation CreateBoundConversionOperation(BoundConversion boundConversio
else
{
Lazy<IOperation> operand = new Lazy<IOperation>(() => Create(boundConversion.Operand));

bool isExplicit = boundConversion.ExplicitCastInCode;
bool usesOperatorMethod = boundConversion.ConversionKind == CSharp.ConversionKind.ExplicitUserDefined || boundConversion.ConversionKind == CSharp.ConversionKind.ImplicitUserDefined;
IMethodSymbol operatorMethod = boundConversion.SymbolOpt;
SyntaxNode syntax = boundConversion.Syntax;
Conversion conversion = _semanticModel.GetConversion(syntax);
bool isExplicit = boundConversion.ExplicitCastInCode;
bool throwsExceptionOnFailure = true;
bool isChecked = boundConversion.Checked;
ITypeSymbol type = boundConversion.Type;
Optional<object> constantValue = ConvertToOptional(boundConversion.ConstantValue);
return new LazyConversionExpression(operand, conversionKind, isExplicit, usesOperatorMethod, operatorMethod, syntax, type, constantValue);
return new LazyCSharpConversionExpression(operand, conversion, isExplicit, isChecked, throwsExceptionOnFailure, syntax, type, constantValue);
}
}

private IConversionExpression CreateBoundAsOperatorOperation(BoundAsOperator boundAsOperator)
{
Lazy<IOperation> operand = new Lazy<IOperation>(() => Create(boundAsOperator.Operand));
ConversionKind conversionKind = Semantics.ConversionKind.TryCast;
bool isExplicit = true;
bool usesOperatorMethod = false;
IMethodSymbol operatorMethod = null;
SyntaxNode syntax = boundAsOperator.Syntax;
Conversion conversion = _semanticModel.GetConversion(syntax);
bool isExplicit = true;
bool throwsExceptionOnFailure = false;
bool isChecked = false;
ITypeSymbol type = boundAsOperator.Type;
Optional<object> constantValue = ConvertToOptional(boundAsOperator.ConstantValue);
return new LazyConversionExpression(operand, conversionKind, isExplicit, usesOperatorMethod, operatorMethod, syntax, type, constantValue);
return new LazyCSharpConversionExpression(operand, conversion, isExplicit, throwsExceptionOnFailure, isChecked, syntax, type, constantValue);
}

private IIsTypeExpression CreateBoundIsOperatorOperation(BoundIsOperator boundIsOperator)
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_2 = 702 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion
Microsoft.CodeAnalysis.CSharp.IConversionExpressionExtensions
static Microsoft.CodeAnalysis.CSharp.IConversionExpressionExtensions.GetConversion(this Microsoft.CodeAnalysis.Semantics.IConversionExpression conversionExpression) -> Microsoft.CodeAnalysis.CSharp.Conversion
static Microsoft.CodeAnalysis.CSharp.Conversion.implicit operator Microsoft.CodeAnalysis.Semantics.CommonConversion(Microsoft.CodeAnalysis.CSharp.Conversion csharpConversion) -> Microsoft.CodeAnalysis.Semantics.CommonConversion
70 changes: 13 additions & 57 deletions src/Compilers/Core/Portable/Generated/Operations.xml.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1296,34 +1296,23 @@ public LazyConditionalChoiceExpression(Lazy<IOperation> condition, Lazy<IOperati
/// </summary>
internal abstract partial class BaseConversionExpression : Operation, IHasOperatorMethodExpression, IConversionExpression
{
protected BaseConversionExpression(ConversionKind conversionKind, bool isExplicit, bool usesOperatorMethod, IMethodSymbol operatorMethod, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
protected BaseConversionExpression(CommonConversion conversion, bool isExplicitInCode, bool throwsExceptionOnFailure, bool isChecked, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
base(OperationKind.ConversionExpression, syntax, type, constantValue)
{
ConversionKind = conversionKind;
IsExplicit = isExplicit;
UsesOperatorMethod = usesOperatorMethod;
OperatorMethod = operatorMethod;
Conversion = conversion;
IsExplicitInCode = isExplicitInCode;
ThrowsExceptionOnFailure = throwsExceptionOnFailure;
IsChecked = isChecked;
}
/// <summary>
/// Value to be converted.
/// </summary>

public abstract IOperation Operand { get; }
/// <summary>
/// Kind of conversion.
/// </summary>
public ConversionKind ConversionKind { get; }
/// <summary>
/// True if and only if the conversion is indicated explicity by a cast operation in the source code.
/// </summary>
public bool IsExplicit { get; }
/// <summary>
/// True if and only if the operation is performed by an operator method.
/// </summary>
public bool UsesOperatorMethod { get; }
/// <summary>
/// Operation method used by the operation, null if the operation does not use an operator method.
/// </summary>
public IMethodSymbol OperatorMethod { get; }
public CommonConversion Conversion { 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?

public bool ThrowsExceptionOnFailure { get; }
public bool IsChecked { get; }
public abstract string LanguageName { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Has LanguageName been replaced by IOperation.Language?

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.

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?

public virtual IMethodSymbol OperatorMethod => Conversion.MethodSymbol;
Copy link
Member

Choose a reason for hiding this comment

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

Why virtual?

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 was virtual because it needed to be redefined in the C# version, but that's not necessary any more. I'll remove it.

public override IEnumerable<IOperation> Children
{
get
Expand All @@ -1341,39 +1330,6 @@ public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, T
}
}

/// <summary>
/// Represents a conversion operation.
/// </summary>
internal sealed partial class ConversionExpression : BaseConversionExpression, IHasOperatorMethodExpression, IConversionExpression
{
public ConversionExpression(IOperation operand, ConversionKind conversionKind, bool isExplicit, bool usesOperatorMethod, IMethodSymbol operatorMethod, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) :
base(conversionKind, isExplicit, usesOperatorMethod, operatorMethod, syntax, type, constantValue)
{
Operand = operand;
}
/// <summary>
/// Value to be converted.
/// </summary>
public override IOperation Operand { get; }
}

/// <summary>
/// Represents a conversion operation.
/// </summary>
internal sealed partial class LazyConversionExpression : BaseConversionExpression, IHasOperatorMethodExpression, IConversionExpression
{
private readonly Lazy<IOperation> _lazyOperand;

public LazyConversionExpression(Lazy<IOperation> operand, ConversionKind conversionKind, bool isExplicit, bool usesOperatorMethod, IMethodSymbol operatorMethod, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue) : base(conversionKind, isExplicit, usesOperatorMethod, operatorMethod, syntax, type, constantValue)
{
_lazyOperand = operand ?? throw new System.ArgumentNullException(nameof(operand));
}
/// <summary>
/// Value to be converted.
/// </summary>
public override IOperation Operand => _lazyOperand.Value;
}

/// <remarks>
/// This interface is reserved for implementation by its associated APIs. We reserve the right to
/// change it in the future.
Expand Down
54 changes: 54 additions & 0 deletions src/Compilers/Core/Portable/Operations/CommonConversion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.CodeAnalysis.Semantics
{
/// <summary>
/// Represents the common, language-agnostic elements of a conversion.
/// </summary>
/// <remarks>
/// This interface is reserved for implementation by its associated APIs. We reserve the right to
/// change it in the future.
Copy link
Member

Choose a reason for hiding this comment

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

This struct ... or This 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.

I'll just remove it. Can't really implement a struct :). I'll remark that we reserve the right to change it in the future.

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

/// </remarks>
public struct CommonConversion
{
internal CommonConversion(bool exists, bool isIdentity, bool isNumeric, bool isReference, bool isUserDefined, IMethodSymbol methodSymbol)
{
Exists = exists;
IsIdentity = isIdentity;
IsNumeric = isNumeric;
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


/// <summary>
/// Returns true if the conversion exists, as defined by the target language.
/// </summary>
/// <remarks>
/// 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 { get; }
/// <summary>
/// Returns true if the conversion is an identity conversion.
/// </summary>
public bool IsIdentity { get; }
/// <summary>
/// Returns true if the conversion is a numeric conversion.
/// </summary>
public bool IsNumeric { get; }
/// <summary>
/// Returns true if the conversion is a reference conversion.
/// </summary>
public bool IsReference { get; }
/// <summary>
/// Returns true if the conversion is a user-defined conversion.
/// </summary>
public bool IsUserDefined { get; }
Copy link
Member

Choose a reason for hiding this comment

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

bool IsUserDefined => MethodSymbol != null;

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

/// <summary>
/// Returns the method used to perform the conversion for a user-defined conversion if <see cref="IsUserDefined"/> is true.
/// Otherwise, returns null.
/// </summary>
public IMethodSymbol MethodSymbol { get; }
}
}
30 changes: 26 additions & 4 deletions src/Compilers/Core/Portable/Operations/IConversionExpression.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System;

namespace Microsoft.CodeAnalysis.Semantics
{
Expand All @@ -17,14 +17,36 @@ public interface IConversionExpression : IHasOperatorMethodExpression
/// Value to be converted.
/// </summary>
IOperation Operand { get; }

#pragma warning disable RS0010 // Avoid using cref tags with a prefix
/// <summary>
/// Kind of conversion.
/// Gets the underlying common conversion information.
/// </summary>
ConversionKind ConversionKind { get; }
/// <remarks>
/// If you need conversion information that is language specific, use either
/// <see cref="T:Microsoft.CodeAnalysis.CSharp.IConversionExpressionExtensions.GetConversion(IConversionExpression)"/> or
/// <see cref="T:Microsoft.CodeAnalysis.VisualBasic.GetConversion(IConversionExpression)"/>.
/// </remarks>
#pragma warning restore RS0010 // Avoid using cref tags with a prefix
CommonConversion Conversion { get; }
/// <summary>
/// True if and only if the conversion is indicated explicity by a cast operation in the source code.
/// </summary>
bool IsExplicit { get; }
bool IsExplicitInCode { get; }
/// <summary>
/// True if the conversion will fail with a <see cref="InvalidCastException"/> at runtime if the cast fails. This is false for C#'s
/// <code>as</code> operator and for VB's <code>TryCast</code> operator.
Copy link
Member

Choose a reason for hiding this comment

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

it should also be false for thigns like a numeric conversion in an unchecked context. ensure we test 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.

No. That's not going to fail with an InvalidCastException.

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 we need a different name for this. Perhaps invert the condition and call it IsTryCast?

Copy link
Member

Choose a reason for hiding this comment

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

IsTryCast sounds good.

Copy link
Member

Choose a reason for hiding this comment

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

👍 for me as well.

Copy link
Member

Choose a reason for hiding this comment

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

will IsChecked only be true for numeric cases?

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to IsTryCast

Copy link
Member Author

Choose a reason for hiding this comment

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

@CyrusNajmabadi I'm not sure. I'll add a test case to verify that behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Comment appears to be stale since it says false for as and TryCast.

/// </summary>
bool ThrowsExceptionOnFailure { get; }
/// <summary>
/// True if the conversion can fail at runtime with an overflow exception. This corresponds to C# checked and unchecked blocks.
/// </summary>
bool IsChecked { get; }
Copy link
Member

Choose a reason for hiding this comment

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

i'ts not clear ot me how this property and the one above are related.

Copy link
Member Author

Choose a reason for hiding this comment

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

They're different failure modes, and I'm not of the opinion we should try to unify them.

/// <summary>
/// The language that defined this conversion. Possible values are <see cref="LanguageNames.CSharp"/> and
/// <see cref="LanguageNames.VisualBasic"/>.
/// </summary>
string LanguageName { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Can all of these properties move to CommonConversion?

Copy link
Member Author

Choose a reason for hiding this comment

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

@cston I'm not sure. As I mentioned to @CyrusNajmabadi below, I'm ok with moving LanguageName. However, for the rest of the fields, you can't infer everything here from the VB or C# Conversion structs. For C#, you could infer IsExplicitInCode, but for VB you'll need the corresponding BoundNode for the specific conversion. As far as I can tell, the existing Conversion structs do not provide any info on whether it was a checked or TryCast at all, so we'll again need the corresponding BoundNode to fill that in. Given that, I think I'd prefer to leave them in the IConversionExpression.

}
}

14 changes: 12 additions & 2 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ Microsoft.CodeAnalysis.Semantics.CaseKind.Pattern = 5 -> Microsoft.CodeAnalysis.
Microsoft.CodeAnalysis.Semantics.CaseKind.Range = 3 -> Microsoft.CodeAnalysis.Semantics.CaseKind
Microsoft.CodeAnalysis.Semantics.CaseKind.Relational = 2 -> Microsoft.CodeAnalysis.Semantics.CaseKind
Microsoft.CodeAnalysis.Semantics.CaseKind.SingleValue = 1 -> Microsoft.CodeAnalysis.Semantics.CaseKind
Microsoft.CodeAnalysis.Semantics.CommonConversion
Microsoft.CodeAnalysis.Semantics.CommonConversion.Exists.get -> bool
Microsoft.CodeAnalysis.Semantics.CommonConversion.IsIdentity.get -> bool
Microsoft.CodeAnalysis.Semantics.CommonConversion.IsNumeric.get -> bool
Microsoft.CodeAnalysis.Semantics.CommonConversion.IsReference.get -> bool
Microsoft.CodeAnalysis.Semantics.CommonConversion.IsUserDefined.get -> bool
Microsoft.CodeAnalysis.Semantics.CommonConversion.MethodSymbol.get -> Microsoft.CodeAnalysis.IMethodSymbol
Microsoft.CodeAnalysis.Semantics.ConversionKind
Microsoft.CodeAnalysis.Semantics.ConversionKind.Basic = 3 -> Microsoft.CodeAnalysis.Semantics.ConversionKind
Microsoft.CodeAnalysis.Semantics.ConversionKind.CSharp = 4 -> Microsoft.CodeAnalysis.Semantics.ConversionKind
Expand Down Expand Up @@ -387,9 +394,12 @@ Microsoft.CodeAnalysis.Semantics.IConditionalChoiceExpression.IfTrueValue.get ->
Microsoft.CodeAnalysis.Semantics.IConstantPattern
Microsoft.CodeAnalysis.Semantics.IConstantPattern.Value.get -> Microsoft.CodeAnalysis.IOperation
Microsoft.CodeAnalysis.Semantics.IConversionExpression
Microsoft.CodeAnalysis.Semantics.IConversionExpression.ConversionKind.get -> Microsoft.CodeAnalysis.Semantics.ConversionKind
Microsoft.CodeAnalysis.Semantics.IConversionExpression.IsExplicit.get -> bool
Microsoft.CodeAnalysis.Semantics.IConversionExpression.Conversion.get -> Microsoft.CodeAnalysis.Semantics.CommonConversion
Microsoft.CodeAnalysis.Semantics.IConversionExpression.IsChecked.get -> bool
Microsoft.CodeAnalysis.Semantics.IConversionExpression.IsExplicitInCode.get -> bool
Microsoft.CodeAnalysis.Semantics.IConversionExpression.LanguageName.get -> string
Microsoft.CodeAnalysis.Semantics.IConversionExpression.Operand.get -> Microsoft.CodeAnalysis.IOperation
Microsoft.CodeAnalysis.Semantics.IConversionExpression.ThrowsExceptionOnFailure.get -> bool
Microsoft.CodeAnalysis.Semantics.IDeclarationPattern
Microsoft.CodeAnalysis.Semantics.IDeclarationPattern.DeclaredSymbol.get -> Microsoft.CodeAnalysis.ISymbol
Microsoft.CodeAnalysis.Semantics.IDefaultCaseClause
Expand Down
Loading