// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; using static CodeGenerationHelpers; using static CSharpSyntaxTokens; using static SyntaxFactory; internal static class ExpressionGenerator { public static ExpressionSyntax GenerateExpression( TypedConstant typedConstant) { switch (typedConstant.Kind) { case TypedConstantKind.Primitive: case TypedConstantKind.Enum: return GenerateExpression(typedConstant.Type, typedConstant.Value, canUseFieldReference: true); case TypedConstantKind.Type: return typedConstant.Value is ITypeSymbol typeSymbol ? TypeOfExpression(typeSymbol.GenerateTypeSyntax()) : GenerateNullLiteral(); case TypedConstantKind.Array: return typedConstant.IsNull ? GenerateNullLiteral() : ImplicitArrayCreationExpression( InitializerExpression(SyntaxKind.ArrayInitializerExpression, [.. typedConstant.Values.Select(v => GenerateExpression(v))])); default: return GenerateNullLiteral(); } } private static ExpressionSyntax GenerateNullLiteral() => LiteralExpression(SyntaxKind.NullLiteralExpression); internal static ExpressionSyntax GenerateExpression( ITypeSymbol? type, object? value, bool canUseFieldReference) { if (type != null && value != null) { if (type is INamedTypeSymbol { TypeKind: TypeKind.Enum } enumType) return (ExpressionSyntax)CSharpFlagsEnumGenerator.Instance.CreateEnumConstantValue(enumType, value); if (type.IsNullable(out var underlyingType)) { // If the type of the argument is T?, then the type of the supplied default value can either be T // (e.g. int? x = 5) or it can be T? (e.g. SomeStruct? x = null). The below statement handles the case // where the type of the supplied default value is T. return GenerateExpression(underlyingType, value, canUseFieldReference); } } return GenerateNonEnumValueExpression(type, value, canUseFieldReference); } internal static ExpressionSyntax GenerateNonEnumValueExpression(ITypeSymbol? type, object? value, bool canUseFieldReference) { var intermediaryValue = value switch { bool val => GenerateBooleanLiteralExpression(val), string val => GenerateStringLiteralExpression(val), char val => GenerateCharLiteralExpression(val), sbyte val => GenerateLiteralExpression(type, val, LiteralSpecialValues.SByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v), x => x < 0, x => (sbyte)-x, "128"), short val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int16SpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v), x => x < 0, x => (short)-x, "32768"), int val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int32SpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, "2147483648"), long val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int64SpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, "9223372036854775808"), byte val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.ByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v)), ushort val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt16SpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, (uint)v)), uint val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt32SpecialValues, formatString: null, canUseFieldReference, Literal), ulong val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt64SpecialValues, formatString: null, canUseFieldReference, Literal), float val => GenerateSingleLiteralExpression(type, val, canUseFieldReference), double val => GenerateDoubleLiteralExpression(type, val, canUseFieldReference), decimal val => GenerateLiteralExpression(type, val, LiteralSpecialValues.DecimalSpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, integerMinValueString: null), _ => type == null || type.IsReferenceType || type is IPointerTypeSymbol || type.IsNullable() ? GenerateNullLiteral() : (ExpressionSyntax)CSharpSyntaxGeneratorInternal.Instance.DefaultExpression(type), }; if (intermediaryValue is not LiteralExpressionSyntax and not PrefixUnaryExpressionSyntax) return intermediaryValue; // Round trip expressions through the parser, so we can be sure to get exactly the same syntax the parser would produce. return ParseExpression(intermediaryValue.ToString()); } private static ExpressionSyntax GenerateBooleanLiteralExpression(bool val) { return LiteralExpression(val ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression); } private static ExpressionSyntax GenerateStringLiteralExpression(string val) { var valueString = SymbolDisplay.FormatLiteral(val, quote: true); return LiteralExpression( SyntaxKind.StringLiteralExpression, Literal(valueString, val)); } private static ExpressionSyntax GenerateCharLiteralExpression(char val) { var literal = SymbolDisplay.FormatLiteral(val, quote: true); return LiteralExpression( SyntaxKind.CharacterLiteralExpression, Literal(literal, val)); } private static string DetermineSuffix(ITypeSymbol? type, object value) { if (value is float) { var stringValue = ((IFormattable)value).ToString("R", CultureInfo.InvariantCulture); var isNotSingle = !IsSpecialType(type, SpecialType.System_Single); var containsDoubleCharacter = stringValue.Contains('E') || stringValue.Contains('e') || stringValue.Contains('.') || stringValue.Contains('+') || stringValue.Contains('-'); if (isNotSingle || containsDoubleCharacter) { return "F"; } } if (value is double && !IsSpecialType(type, SpecialType.System_Double)) { return "D"; } if (value is uint && !IsSpecialType(type, SpecialType.System_UInt32)) { return "U"; } if (value is long && !IsSpecialType(type, SpecialType.System_Int64)) { return "L"; } if (value is ulong && !IsSpecialType(type, SpecialType.System_UInt64)) { return "UL"; } if (value is decimal d) { var scale = d.GetScale(); var isNotDecimal = !IsSpecialType(type, SpecialType.System_Decimal); var isOutOfRange = d is < long.MinValue or > long.MaxValue; var scaleIsNotZero = scale != 0; if (isNotDecimal || isOutOfRange || scaleIsNotZero) { return "M"; } } return string.Empty; } private static ExpressionSyntax GenerateDoubleLiteralExpression(ITypeSymbol? type, double value, bool canUseFieldReference) { if (!canUseFieldReference) { if (double.IsNaN(value)) { return BinaryExpression(SyntaxKind.DivideExpression, GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } else if (double.IsPositiveInfinity(value)) { return BinaryExpression(SyntaxKind.DivideExpression, GenerateDoubleLiteralExpression(type: null, 1.0, canUseFieldReference: false), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } else if (double.IsNegativeInfinity(value)) { return BinaryExpression(SyntaxKind.DivideExpression, PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateDoubleLiteralExpression(null, 1.0, false)), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } } return GenerateLiteralExpression( type, value, LiteralSpecialValues.DoubleSpecialValues, formatString: "R", canUseFieldReference, Literal, x => x < 0, x => -x, integerMinValueString: null); } private static ExpressionSyntax GenerateSingleLiteralExpression(ITypeSymbol? type, float value, bool canUseFieldReference) { if (!canUseFieldReference) { if (float.IsNaN(value)) { return BinaryExpression(SyntaxKind.DivideExpression, GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false), GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false)); } else if (float.IsPositiveInfinity(value)) { return BinaryExpression(SyntaxKind.DivideExpression, GenerateSingleLiteralExpression(type: null, 1.0F, canUseFieldReference: false), GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false)); } else if (float.IsNegativeInfinity(value)) { return BinaryExpression(SyntaxKind.DivideExpression, PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateSingleLiteralExpression(null, 1.0F, false)), GenerateSingleLiteralExpression(null, 0.0F, false)); } } return GenerateLiteralExpression( type, value, LiteralSpecialValues.SingleSpecialValues, formatString: "R", canUseFieldReference, Literal, x => x < 0, x => -x, null); } private static ExpressionSyntax GenerateNonNegativeLiteralExpression<T>( ITypeSymbol? type, T value, IEnumerable<KeyValuePair<T, string>> constants, string? formatString, bool canUseFieldReference, Func<string, T, SyntaxToken> tokenFactory) where T : IEquatable<T> { return GenerateLiteralExpression( type, value, constants, formatString, canUseFieldReference, tokenFactory, isNegative: x => false, negate: t => throw new InvalidOperationException(), integerMinValueString: null); } private static ExpressionSyntax GenerateLiteralExpression<T>( ITypeSymbol? type, T value, IEnumerable<KeyValuePair<T, string>> constants, string? formatString, bool canUseFieldReference, Func<string, T, SyntaxToken> tokenFactory, Func<T, bool> isNegative, Func<T, T> negate, string? integerMinValueString) where T : IEquatable<T> { if (canUseFieldReference) { var result = GenerateFieldReference(type, value, constants); if (result != null) { return result; } } var negative = isNegative(value); var nonNegativeValue = negative ? negate(value) : value; var suffix = DetermineSuffix(type, nonNegativeValue); var stringValue = negative && nonNegativeValue.Equals(value) ? (integerMinValueString ?? throw ExceptionUtilities.Unreachable()) : ((IFormattable)nonNegativeValue).ToString(formatString, CultureInfo.InvariantCulture) + suffix; var literal = LiteralExpression( SyntaxKind.NumericLiteralExpression, tokenFactory(stringValue, nonNegativeValue)); return negative ? PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, literal) : literal; } private static ExpressionSyntax? GenerateFieldReference<T>(ITypeSymbol? type, T value, IEnumerable<KeyValuePair<T, string>> constants) where T : IEquatable<T> { foreach (var constant in constants) { if (constant.Key.Equals(value)) { var memberAccess = GenerateMemberAccess("System", typeof(T).Name); if (type is not null and not IErrorTypeSymbol) { memberAccess = memberAccess.WithAdditionalAnnotations(SpecialTypeAnnotation.Create(type.SpecialType)); } var result = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, memberAccess, IdentifierName(constant.Value)); return result.WithAdditionalAnnotations(Simplifier.Annotation); } } return null; } private static ExpressionSyntax GenerateMemberAccess(params ReadOnlySpan<string> names) { ExpressionSyntax result = IdentifierName(GlobalKeyword); for (var i = 0; i < names.Length; i++) { var name = IdentifierName(names[i]); result = i == 0 ? AliasQualifiedName((IdentifierNameSyntax)result, name) : MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, result, name); } return result.WithAdditionalAnnotations(Simplifier.Annotation); } }