diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9592e32d17..c1fcaee699d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,11 +43,6 @@ jobs: with: dotnet-version: 3.1.301 - - name: Update NuGet config - # workaround for bug in setup-dotnet action (the config samples for authenticated feeds don't work) - # encryption does not work on Linux or MacOS - run: dotnet nuget update source Official --username bicep --password ${{ secrets.MSAZURE_NUGET_AUTH }} --valid-authentication-types=basic --configfile ./NuGet.config --store-password-in-clear-text - - name: Build run: dotnet build --configuration ${{ matrix.configuration }} @@ -119,11 +114,6 @@ jobs: with: dotnet-version: 3.1.301 - - name: Update NuGet config - # workaround for bug in setup-dotnet action (the config samples for authenticated feeds don't work) - # encryption does not work on Linux or MacOS - run: dotnet nuget update source Official --username bicep --password ${{ secrets.MSAZURE_NUGET_AUTH }} --valid-authentication-types=basic --configfile ./NuGet.config --store-password-in-clear-text - - name: Setup Node.js uses: actions/setup-node@v2.1.1 with: diff --git a/Bicep.sln b/Bicep.sln index 9496adc09ef..c76a9706e09 100644 --- a/Bicep.sln +++ b/Bicep.sln @@ -29,6 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Core.Samples", "src\B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Cli.UnitTests", "src\Bicep.Cli.UnitTests\Bicep.Cli.UnitTests.csproj", "{29A1AE2B-D47D-41DD-9824-279475D9B6C2}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9EA83F16-962D-4596-A6E0-DCFF2D8E1FA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arm.Expression", "src\Arm.Expression\Arm.Expression.csproj", "{3229C864-9524-4FDB-BE76-3714ABE7F4E2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,6 +75,10 @@ Global {29A1AE2B-D47D-41DD-9824-279475D9B6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {29A1AE2B-D47D-41DD-9824-279475D9B6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {29A1AE2B-D47D-41DD-9824-279475D9B6C2}.Release|Any CPU.Build.0 = Release|Any CPU + {3229C864-9524-4FDB-BE76-3714ABE7F4E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3229C864-9524-4FDB-BE76-3714ABE7F4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3229C864-9524-4FDB-BE76-3714ABE7F4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3229C864-9524-4FDB-BE76-3714ABE7F4E2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,6 +93,7 @@ Global {F7B0F06D-56CE-4F4A-8EF9-ED6EC3D77EE7} = {76F6CBAF-FE81-4B56-B0DB-DE6FD7174771} {A2E4B91C-3B7F-4CB1-B482-BE40F7709EB5} = {FE323E78-E865-46E2-859A-E4F6FB312C0F} {29A1AE2B-D47D-41DD-9824-279475D9B6C2} = {B02251C3-B01E-40EB-B145-2B03F25FE653} + {3229C864-9524-4FDB-BE76-3714ABE7F4E2} = {9EA83F16-962D-4596-A6E0-DCFF2D8E1FA3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21F77282-91E7-4304-B1EF-FADFA4F39E37} diff --git a/NuGet.config b/NuGet.config index 413c87b4b92..248a5bb51aa 100644 --- a/NuGet.config +++ b/NuGet.config @@ -3,6 +3,5 @@ - \ No newline at end of file diff --git a/src/Arm.Expression/Arm.Expression.csproj b/src/Arm.Expression/Arm.Expression.csproj new file mode 100644 index 00000000000..e76c4d6a2a1 --- /dev/null +++ b/src/Arm.Expression/Arm.Expression.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + disable + + + + + + + \ No newline at end of file diff --git a/src/Arm.Expression/Configuration/ExpressionSerializerSettings.cs b/src/Arm.Expression/Configuration/ExpressionSerializerSettings.cs new file mode 100644 index 00000000000..06d7601b3e7 --- /dev/null +++ b/src/Arm.Expression/Configuration/ExpressionSerializerSettings.cs @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +namespace Arm.Expression.Configuration +{ + /// + /// Expression serializer settings. + /// + public class ExpressionSerializerSettings + { + /// + /// Gets or sets a value indicating whether the expression serializer will include the outer [ and ] + /// characters when serializing a LanguageExpression. This setting is ignored if the serializer decides to serialize + /// the expression into a single string literal. + /// + public bool IncludeOuterSquareBrackets { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the expression serializer will serialize a single string literal + /// expression as a string + /// + public ExpressionSerializerSingleStringHandling SingleStringHandling { get; set; } = ExpressionSerializerSingleStringHandling.SerializeAsJTokenExpression; + } +} diff --git a/src/Arm.Expression/Configuration/ExpressionSerializerSingleStringHandling.cs b/src/Arm.Expression/Configuration/ExpressionSerializerSingleStringHandling.cs new file mode 100644 index 00000000000..e4996e7b042 --- /dev/null +++ b/src/Arm.Expression/Configuration/ExpressionSerializerSingleStringHandling.cs @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +namespace Arm.Expression.Configuration +{ + /// + /// Configures how expression serializer handles single string literal expressions. + /// + public enum ExpressionSerializerSingleStringHandling + { + /// + /// Not specified. + /// + NotSpecified = 0, + + /// + /// Serializes the single string literal as a string literal expression of the form + /// ['string contents']. This is the default behavior. The behavior does not apply for + /// Language expressions that are not a single JTokenExpression with a string value. + /// + SerializeAsJTokenExpression = 1, + + /// + /// Serializes the single string literal as a string value. If the string begins with a + /// [ character, it will be escaped with [[. The behavior does not apply for + /// Language expressions that are not a single JTokenExpression with a string value. + /// + SerializeAsString = 2, + } +} diff --git a/src/Arm.Expression/Expressions/ExpressionSerializer.cs b/src/Arm.Expression/Expressions/ExpressionSerializer.cs new file mode 100644 index 00000000000..86e3625f6a0 --- /dev/null +++ b/src/Arm.Expression/Expressions/ExpressionSerializer.cs @@ -0,0 +1,337 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +using Arm.Expression.Configuration; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Text; + +namespace Arm.Expression.Expressions +{ + /// + /// Serializes language expressions into strings. + /// + public class ExpressionSerializer + { + /// + /// The serialization settings. + /// + private readonly ExpressionSerializerSettings settings; + + /// + /// Initializes a new instance of the class. + /// + /// The optional serializer settings + public ExpressionSerializer(ExpressionSerializerSettings settings = null) + { + // Note(majastrz): Use default settings if not specified. + this.settings = settings ?? new ExpressionSerializerSettings(); + } + + /// + /// Serializes a language expression into a string. + /// + /// The expression + public string SerializeExpression(LanguageExpression expression) + { + var buffer = new StringBuilder(); + + if (!ExpressionSerializer.TryWriteSingleStringToBuffer(buffer: buffer, expression: expression, settings: this.settings)) + { + // Note(majastrz): The expression is not a single string expression or the single string serialization is disabled + + // Note(majastrz): Include outer brackets when serializing the expression if feature is enabled + ExpressionSerializer.WriteExpressionToBuffer( + buffer: buffer, + expression: expression, + prefix: this.settings.IncludeOuterSquareBrackets ? "[" : null, + suffix: this.settings.IncludeOuterSquareBrackets ? "]" : null); + } + + return buffer.ToString(); + } + + /// + /// If the expression is a single string JTokenExpression and serialization as string is enabled, it performs the serialization. + /// + /// The buffer + /// The expression + /// The settings + /// True if expression has been written out to the buffer or false otherwise. + private static bool TryWriteSingleStringToBuffer(StringBuilder buffer, LanguageExpression expression, ExpressionSerializerSettings settings) + { + if (settings.SingleStringHandling == ExpressionSerializerSingleStringHandling.SerializeAsString && expression is JTokenExpression valueExpression) + { + var value = ExpressionSerializer.GetValueFromValueExpression(valueExpression: valueExpression); + if (value.Type == JTokenType.String) + { + var valueStr = value.ToString(); + + // Note(majastrz): Add escape bracket if needed. + if (valueStr.Length > 0 && valueStr[0] == '[') + { + buffer.Append(value: '['); + } + + buffer.Append(value: valueStr); + + return true; + } + } + + // Note(majastrz): Returning false REQUIRES that buffer not be modified in any way. + return false; + } + + /// + /// Writes the serialized expression to the buffer. + /// + /// The buffer + /// The expression + /// The optional prefix + /// The optional suffix + private static void WriteExpressionToBuffer(StringBuilder buffer, LanguageExpression expression, string prefix = null, string suffix = null) + { + ExpressionSerializer.ValidateExpression(expression: expression); + + if (prefix != null) + { + buffer.Append(value: prefix); + } + + switch (expression) + { + case FunctionExpression functionExpression: + ExpressionSerializer.WriteFunctionExpressionToBuffer(buffer: buffer, functionExpression: functionExpression); + break; + + case JTokenExpression valueExpression: + ExpressionSerializer.WriteValueExpressionToBuffer(buffer: buffer, valueExpression: valueExpression); + break; + + default: + throw new InvalidOperationException($"Unexpected expression type '{expression.GetType().Name}'"); + } + + if (suffix != null) + { + buffer.Append(value: suffix); + } + } + + /// + /// Writes the function expression to the buffer. + /// + /// The buffer + /// The function expression + private static void WriteFunctionExpressionToBuffer(StringBuilder buffer, FunctionExpression functionExpression) + { + ExpressionSerializer.ValidateFunctionExpression(functionExpression: functionExpression); + + // Note(majastrz): Add function name + buffer.Append(value: functionExpression.Function); + + // Note(majastrz): Opening paren + buffer.Append(value: '('); + + // Note(majastrz): Add parameter expressions and delimit with comma + const string ParameterDelimiter = ", "; + foreach (var parameterExpression in functionExpression.Parameters) + { + ExpressionSerializer.WriteExpressionToBuffer(buffer: buffer, expression: parameterExpression, suffix: ParameterDelimiter); + } + + // Note(majastrz): Remove last param delimiter + if (functionExpression.Parameters.Any()) + { + buffer.Remove(startIndex: buffer.Length - ParameterDelimiter.Length, length: ParameterDelimiter.Length); + } + + // Note(majastrz): Closing paren + buffer.Append(value: ')'); + + // Note(majastrz): Add the property expressions with correct enclosing characters + foreach (var propertyExpression in functionExpression.Properties) + { + ExpressionSerializer.WritePropertyExpressionToBuffer(buffer: buffer, propertyExpression: propertyExpression); + } + } + + /// + /// Writes the specified property expression to the buffer. + /// + /// The buffer + /// The property expression + private static void WritePropertyExpressionToBuffer(StringBuilder buffer, LanguageExpression propertyExpression) + { + ExpressionSerializer.ValidateExpression(expression: propertyExpression); + + switch (propertyExpression) + { + case FunctionExpression functionExpression: + // Note(majastrz): Functions in properties should be enclosed in brackets + ExpressionSerializer.WriteExpressionToBuffer(buffer: buffer, expression: functionExpression, prefix: "[", suffix: "]"); + + return; + + case JTokenExpression valueExpression: + var value = ExpressionSerializer.GetValueFromValueExpression(valueExpression: valueExpression); + + switch (value.Type) + { + case JTokenType.String: + var valueStr = value.ToString(); + + if (ExpressionSerializer.IsIdentifier(value: valueStr)) + { + // Note(majastrz): The property expression is an identifier. We can serialize it with a leading dot and without any enclosing quotes. + buffer.Append(value: '.'); + buffer.Append(value: valueStr); + } + else + { + // Note(majastrz): The property expression has to be enclosed in brackets because it's not an identifier. + ExpressionSerializer.WriteExpressionToBuffer(buffer: buffer, expression: valueExpression, prefix: "[", suffix: "]"); + } + + return; + + case JTokenType.Integer: + // Note(majastrz): Indexes should be enclosed in brackets. + buffer.Append(value: '['); + buffer.Append(value: value); + buffer.Append(value: ']'); + + return; + + default: + // Note(majastrz): JTokenValue can only be created with string or int value. + throw new InvalidOperationException(message: $"JTokenExpression has a value of unexpected type '{value.Type}'."); + } + + default: + throw new InvalidOperationException($"Unexpected expression type '{propertyExpression.GetType().Name}'"); + } + } + + /// + /// Writes the value expression to the buffer. + /// + /// The buffer + /// The value expression + private static void WriteValueExpressionToBuffer(StringBuilder buffer, JTokenExpression valueExpression) + { + ExpressionSerializer.ValidateValueExpression(valueExpression: valueExpression); + + var value = ExpressionSerializer.GetValueFromValueExpression(valueExpression: valueExpression); + + switch (value.Type) + { + case JTokenType.Integer: + // Note(majastrz): Integers are serialized as-is + buffer.Append(value: value); + + return; + + case JTokenType.String: + WriteEscapedStringLiteral(buffer, value.ToString()); + + return; + + default: + // Note(majastrz): JTokenValue can only be created with string or int value. + throw new InvalidOperationException($"JTokenExpression has a value of unexpected type '{value.Type}'."); + } + } + + private static void WriteEscapedStringLiteral(StringBuilder buffer, string value) + { + // Note(majastrz): Strings are serialized enclosed in single quotes. Double single quote character is used to escape a single quote in the string. + buffer.Append(value: '\''); + for (var i = 0; i < value.Length; i++) + { + if (value[i] == '\'') + { + buffer.Append('\''); + } + buffer.Append(value[i]); + } + buffer.Append(value: '\''); + } + + /// + /// Gets a value from the value expression. + /// + /// The value expression + private static JToken GetValueFromValueExpression(JTokenExpression valueExpression) + { + // Note(majastrz): EvaluateExpression on JTokenExpression just returns the value and does not use the context at all. + // The constructor of JTokenExpression does not allow you to create one with a null value, so we don't really need to check + return valueExpression.Value; + } + + /// + /// Determines if the specified string is an identifier. + /// + /// The string to check + private static bool IsIdentifier(string value) + { + return !string.IsNullOrEmpty(value: value) && value.All(IsSupportedIdentifierCharacter); + } + + /// + /// Determines whether character is a supported identifier character. + /// + /// Input character. + public static bool IsSupportedIdentifierCharacter(char character) + { + return char.IsLetterOrDigit(character) || character == '$' || character == '_'; + } + + /// + /// Validates that the specified function expression. Does not perform recursive validation. + /// + /// The function expression + private static void ValidateFunctionExpression(FunctionExpression functionExpression) + { + ExpressionSerializer.ValidateExpression(expression: functionExpression); + ExpressionSerializer.ValidateExpressionArray(functionExpression.Parameters); + ExpressionSerializer.ValidateExpressionArray(functionExpression.Properties); + } + + /// + /// Validates the specified value expression. Does not perform recursive validation. + /// + /// The value expression + private static void ValidateValueExpression(JTokenExpression valueExpression) + { + ExpressionSerializer.ValidateExpression(expression: valueExpression); + } + + /// + /// Validates that the specified expression is not null. Does not perform recursive validation. + /// + /// The expression + private static void ValidateExpression(LanguageExpression expression) + { + if (expression == null) + { + throw new ArgumentNullException(nameof(expression)); + } + } + + /// + /// Validates the specified array of property expressions. Does not perform recursive validation. + /// + /// The expressions array + private static void ValidateExpressionArray(LanguageExpression[] expressions) + { + if (expressions == null) + { + throw new ArgumentNullException(nameof(expressions)); + } + } + } +} diff --git a/src/Arm.Expression/Expressions/FunctionExpression.cs b/src/Arm.Expression/Expressions/FunctionExpression.cs new file mode 100644 index 00000000000..c568b2e9b08 --- /dev/null +++ b/src/Arm.Expression/Expressions/FunctionExpression.cs @@ -0,0 +1,18 @@ +namespace Arm.Expression.Expressions +{ + public class FunctionExpression : LanguageExpression + { + public string Function { get; } + + public LanguageExpression[] Parameters { get; } + + public LanguageExpression[] Properties { get; } + + public FunctionExpression(string function, LanguageExpression[] parameters, LanguageExpression[] properties) + { + this.Function = function; + this.Parameters = parameters; + this.Properties = properties; + } + } +} \ No newline at end of file diff --git a/src/Arm.Expression/Expressions/JTokenExpression.cs b/src/Arm.Expression/Expressions/JTokenExpression.cs new file mode 100644 index 00000000000..7cf4e14e411 --- /dev/null +++ b/src/Arm.Expression/Expressions/JTokenExpression.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json.Linq; + +namespace Arm.Expression.Expressions +{ + public class JTokenExpression : LanguageExpression + { + public JToken Value { get; } + + public JTokenExpression(string value) + { + this.Value = value; + } + + public JTokenExpression(int value) + { + this.Value = value; + } + } +} \ No newline at end of file diff --git a/src/Arm.Expression/Expressions/LanguageExpression.cs b/src/Arm.Expression/Expressions/LanguageExpression.cs new file mode 100644 index 00000000000..d57420e1ff5 --- /dev/null +++ b/src/Arm.Expression/Expressions/LanguageExpression.cs @@ -0,0 +1,7 @@ +namespace Arm.Expression.Expressions +{ + public abstract class LanguageExpression + { + + } +} \ No newline at end of file diff --git a/src/Bicep.Core.UnitTests/Emit/ExpressionConverterTests.cs b/src/Bicep.Core.UnitTests/Emit/ExpressionConverterTests.cs index cae5e17b67b..1198f56f038 100644 --- a/src/Bicep.Core.UnitTests/Emit/ExpressionConverterTests.cs +++ b/src/Bicep.Core.UnitTests/Emit/ExpressionConverterTests.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Azure.ResourceManager.Deployments.Expression.Configuration; -using Azure.ResourceManager.Deployments.Expression.Serializers; +using Arm.Expression.Configuration; +using Arm.Expression.Expressions; using Bicep.Core.Emit; using Bicep.Core.SemanticModel; using Bicep.Core.Syntax; diff --git a/src/Bicep.Core/Bicep.Core.csproj b/src/Bicep.Core/Bicep.Core.csproj index 9a2069adfcf..0c9930125cf 100644 --- a/src/Bicep.Core/Bicep.Core.csproj +++ b/src/Bicep.Core/Bicep.Core.csproj @@ -1,15 +1,19 @@ + netstandard2.0 + + + - all + \ No newline at end of file diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 49f5f75e50a..c8c1133f43f 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Azure.ResourceManager.Deployments.Core.Extensions; using Bicep.Core.Extensions; using Bicep.Core.Parser; using Bicep.Core.TypeSystem; @@ -236,7 +235,7 @@ public DiagnosticBuilderInternal(TextSpan textSpan) public ErrorDiagnostic CannotResolveFunction(string functionName, IList argumentTypes) => new ErrorDiagnostic( TextSpan, "BCP048", - $"Cannot resolve function {functionName}({argumentTypes.Select(t => t.Name).ConcatStrings(", ")})."); + $"Cannot resolve function {functionName}({argumentTypes.Select(t => t.Name).ConcatString(", ")})."); public ErrorDiagnostic StringOrIntegerIndexerRequired(TypeSymbol wrongType) => new ErrorDiagnostic( TextSpan, diff --git a/src/Bicep.Core/Emit/ExpressionConverter.cs b/src/Bicep.Core/Emit/ExpressionConverter.cs index 4d0600c51a3..ad318d6a3f4 100644 --- a/src/Bicep.Core/Emit/ExpressionConverter.cs +++ b/src/Bicep.Core/Emit/ExpressionConverter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Linq; using System.Text; -using Azure.ResourceManager.Deployments.Expression.Expressions; +using Arm.Expression.Expressions; using Bicep.Core.SemanticModel; using Bicep.Core.Syntax; using Newtonsoft.Json; @@ -140,7 +140,7 @@ public static FunctionExpression ToFunctionExpression(this SyntaxBase expression return functionExpression; case JTokenExpression valueExpression: - JToken value = valueExpression.EvaluateExpression(null); + JToken value = valueExpression.Value; switch (value.Type) { @@ -257,10 +257,10 @@ private static LanguageExpression ConvertUnary(UnaryOperationSyntax syntax, Sema return CreateUnaryFunction("not", convertedOperand); case UnaryOperator.Minus: - if (convertedOperand is JTokenExpression literal && literal.EvaluateExpression(null).Type == JTokenType.Integer) + if (convertedOperand is JTokenExpression literal && literal.Value.Type == JTokenType.Integer) { // invert the integer literal - int literalValue = literal.EvaluateExpression(null).Value(); + int literalValue = literal.Value.Value(); return new JTokenExpression(-literalValue); } diff --git a/src/Bicep.Core/Emit/ExpressionEmitter.cs b/src/Bicep.Core/Emit/ExpressionEmitter.cs index df90c59e7f6..a0c72d4001b 100644 --- a/src/Bicep.Core/Emit/ExpressionEmitter.cs +++ b/src/Bicep.Core/Emit/ExpressionEmitter.cs @@ -1,7 +1,6 @@ using System; -using Azure.ResourceManager.Deployments.Expression.Configuration; -using Azure.ResourceManager.Deployments.Expression.Expressions; -using Azure.ResourceManager.Deployments.Expression.Serializers; +using Arm.Expression.Configuration; +using Arm.Expression.Expressions; using Bicep.Core.Syntax; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -77,7 +76,7 @@ public static void EmitLanguageExpression(JsonTextWriter writer, SyntaxBase synt if (converted is JTokenExpression valueExpression) { // the converted expression is a literal - JToken value = valueExpression.EvaluateExpression(null); + JToken value = valueExpression.Value; // for integer literals the expression will look like "[42]" or "[-12]" // while it's still a valid template expression that works in ARM, it looks weird diff --git a/src/Bicep.Core/TypeSystem/BinaryOperatorInfo.cs b/src/Bicep.Core/TypeSystem/BinaryOperatorInfo.cs index 21ad08ec36b..2f72328c93c 100644 --- a/src/Bicep.Core/TypeSystem/BinaryOperatorInfo.cs +++ b/src/Bicep.Core/TypeSystem/BinaryOperatorInfo.cs @@ -1,5 +1,4 @@ using System; -using Azure.ResourceManager.Deployments.Expression.Expressions; using Bicep.Core.Syntax; namespace Bicep.Core.TypeSystem