private TypeSymbol GetBinaryOperationType(TypeManagerContext context, BinaryOperationSyntax syntax) { var errors = new List <ErrorDiagnostic>(); var operandType1 = this.GetTypeInfoInternal(context, syntax.LeftExpression); CollectErrors(errors, operandType1); var operandType2 = this.GetTypeInfoInternal(context, syntax.RightExpression); CollectErrors(errors, operandType2); if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } // operands don't appear to have errors // let's match the operator now var operatorInfo = BinaryOperationResolver.TryMatchExact(syntax.Operator, operandType1, operandType2); if (operatorInfo != null) { // we found a match - use its return type return(operatorInfo.ReturnType); } // we do not have a match // operand types didn't match available operators return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax).BinaryOperatorInvalidType(Operators.BinaryOperatorToText[syntax.Operator], operandType1.Name, operandType2.Name))); }
private TypeSymbol GetTernaryOperationType(TypeManagerContext context, TernaryOperationSyntax syntax) { var errors = new List <ErrorDiagnostic>(); // ternary operator requires the condition to be of bool type var conditionType = this.GetTypeInfoInternal(context, syntax.ConditionExpression); CollectErrors(errors, conditionType); var trueType = this.GetTypeInfoInternal(context, syntax.TrueExpression); CollectErrors(errors, trueType); var falseType = this.GetTypeInfoInternal(context, syntax.FalseExpression); CollectErrors(errors, falseType); if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } var expectedConditionType = LanguageConstants.Bool; if (TypeValidator.AreTypesAssignable(conditionType, expectedConditionType) != true) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.ConditionExpression).ValueTypeMismatch(expectedConditionType.Name))); } // the return type is the union of true and false expression types return(UnionType.Create(trueType, falseType)); }
private TypeSymbol GetPropertyAccessType(TypeManagerContext context, PropertyAccessSyntax syntax) { var errors = new List <ErrorDiagnostic>(); var baseType = this.GetTypeInfoInternal(context, syntax.BaseExpression); CollectErrors(errors, baseType); if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } if (TypeValidator.AreTypesAssignable(baseType, LanguageConstants.Object) != true) { // can only access properties of objects return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.PropertyName).ObjectRequiredForPropertyAccess(baseType))); } if (baseType.TypeKind == TypeKind.Any || !(baseType is ObjectType objectType)) { return(LanguageConstants.Any); } return(this.GetNamedPropertyType(objectType, syntax.PropertyName, syntax.PropertyName.IdentifierName)); }
private TypeSymbol GetUnaryOperationType(TypeManagerContext context, UnaryOperationSyntax syntax) { var errors = new List <ErrorDiagnostic>(); // TODO: When we add number type, this will have to be adjusted var expectedOperandType = syntax.Operator switch { UnaryOperator.Not => LanguageConstants.Bool, UnaryOperator.Minus => LanguageConstants.Int, _ => throw new NotImplementedException() }; var operandType = this.GetTypeInfoInternal(context, syntax.Expression); CollectErrors(errors, operandType); if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } if (TypeValidator.AreTypesAssignable(operandType, expectedOperandType) != true) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax).UnaryOperatorInvalidType(Operators.UnaryOperatorToText[syntax.Operator], operandType.Name))); } return(expectedOperandType); }
private TypeSymbol GetParameterType(TypeManagerContext context, VariableAccessSyntax syntax, ParameterSymbol parameter) { // parameter default values can participate in cycles with their own parameters or other symbols // need to explicitly force a type check to detect that SyntaxBase?expressionToTypeCheck; switch (parameter.Modifier) { case ParameterDefaultValueSyntax defaultValueSyntax: expressionToTypeCheck = defaultValueSyntax.DefaultValue; break; case ObjectSyntax modifierSyntax: // technically it's redundant to type check the entire object because we have existing // compile-time constant checks, but it shouldn't harm anything expressionToTypeCheck = modifierSyntax; break; case null: // there's no default value or modifier - this parameter cannot participate in a cycle expressionToTypeCheck = null; break; default: throw new NotImplementedException($"Unexpected parameter modifier type '{parameter.Modifier.GetType()}"); } if (expressionToTypeCheck != null && ContainsCyclicExpressionError(this.GetTypeInfoInternal(context, expressionToTypeCheck))) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Name.Span).CyclicExpression())); } return(HandleSymbolType(syntax.Name.IdentifierName, syntax.Name.Span, parameter.Type)); }
private TypeSymbol GetVariableAccessType(TypeManagerContext context, VariableAccessSyntax syntax) { var symbol = this.ResolveSymbol(syntax); switch (symbol) { case ErrorSymbol errorSymbol: // variable bind failure - pass the error along return(errorSymbol.ToErrorType()); case ResourceSymbol resource: return(GetResourceType(context, syntax, resource)); case ParameterSymbol parameter: return(GetParameterType(context, syntax, parameter)); case VariableSymbol variable: return(HandleSymbolType(syntax.Name.IdentifierName, syntax.Name.Span, variable.GetVariableType(context))); case OutputSymbol _: return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Name.Span).OutputReferenceNotSupported(syntax.Name.IdentifierName))); default: return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Name.Span).SymbolicNameIsNotAVariableOrParameter(syntax.Name.IdentifierName))); } }
private TypeSymbol GetArrayType(TypeManagerContext context, ArraySyntax array) { var errors = new List <ErrorDiagnostic>(); var itemTypes = new List <TypeSymbol>(array.Children.Length); foreach (SyntaxBase arrayItem in array.Children) { var itemType = this.GetTypeInfoInternal(context, arrayItem); itemTypes.Add(itemType); CollectErrors(errors, itemType); } if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } var aggregatedItemType = UnionType.Create(itemTypes); if (aggregatedItemType.TypeKind == TypeKind.Union || aggregatedItemType.TypeKind == TypeKind.Never) { // array contains a mix of item types or is empty // assume array of any for now return(LanguageConstants.Array); } return(new TypedArrayType(aggregatedItemType)); }
private TypeSymbol GetStringType(TypeManagerContext context, StringSyntax @string) { if (@string.IsInterpolated() == false) { // uninterpolated strings have a known type return(LanguageConstants.String); } var errors = new List <ErrorDiagnostic>(); foreach (var interpolatedExpression in @string.Expressions) { var expressionType = this.GetTypeInfoInternal(context, interpolatedExpression); CollectErrors(errors, expressionType); } if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } // normally we would also do an assignability check, but we allow "any" type in string interpolation expressions // so the assignability check cannot possibly fail (we already collected type errors from the inner expressions at this point) return(LanguageConstants.String); }
private TypeSymbol GetResourceType(TypeManagerContext context, VariableAccessSyntax syntax, ResourceSymbol resource) { // resource bodies can participate in cycles // need to explicitly force a type check on the body if (ContainsCyclicExpressionError(this.GetTypeInfoInternal(context, resource.Body))) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Name.Span).CyclicExpression())); } return(HandleSymbolType(syntax.Name.IdentifierName, syntax.Name.Span, resource.Type)); }
private TypeSymbol GetFunctionCallType(TypeManagerContext context, FunctionCallSyntax syntax) { var errors = new List <ErrorDiagnostic>(); var argumentTypes = syntax.Arguments.Select(syntax1 => GetTypeInfoInternal(context, syntax1)).ToList(); foreach (TypeSymbol argumentType in argumentTypes) { CollectErrors(errors, argumentType); } switch (this.ResolveSymbol(syntax)) { case ErrorSymbol errorSymbol: // function bind failure - pass the error along return(new ErrorTypeSymbol(errors.Concat(errorSymbol.GetDiagnostics()))); case FunctionSymbol function: return(GetFunctionSymbolType(syntax, function, syntax.Arguments, argumentTypes, errors)); default: return(new ErrorTypeSymbol(errors.Append(DiagnosticBuilder.ForPosition(syntax.Name.Span).SymbolicNameIsNotAFunction(syntax.Name.IdentifierName)))); } }
private TypeSymbol GetObjectType(TypeManagerContext context, ObjectSyntax @object) { var errors = new List <ErrorDiagnostic>(); foreach (ObjectPropertySyntax objectProperty in @object.Properties) { var propertyType = this.GetTypeInfoInternal(context, objectProperty); CollectErrors(errors, propertyType); } if (errors.Any()) { return(new ErrorTypeSymbol(errors)); } // type results are cached var properties = @object.Properties .GroupBy(p => p.GetKeyText(), LanguageConstants.IdentifierComparer) .Select(group => new TypeProperty(group.Key, UnionType.Create(group.Select(p => this.GetTypeInfoInternal(context, p.Value))))); // TODO: Add structural naming? return(new NamedObjectType(LanguageConstants.Object.Name, properties, additionalPropertiesType: null)); }
private TypeSymbol GetTypeInfoInternal(TypeManagerContext context, SyntaxBase syntax) { // local function because I don't want this called directly TypeSymbol GetTypeInfoWithoutCache() { if (context.TryMarkVisited(syntax) == false) { // we have already visited this node, which means we have a cycle // all the nodes that were visited are involved in the cycle return(new ErrorTypeSymbol(context.GetVisitedNodes().Select(node => DiagnosticBuilder.ForPosition(node.Span).CyclicExpression()))); } switch (syntax) { case BooleanLiteralSyntax _: return(LanguageConstants.Bool); case NumericLiteralSyntax _: return(LanguageConstants.Int); case StringSyntax @string: return(GetStringType(context, @string)); case ObjectSyntax @object: return(GetObjectType(context, @object)); case ObjectPropertySyntax objectProperty: return(GetTypeInfoInternal(context, objectProperty.Value)); case ArraySyntax array: return(GetArrayType(context, array)); case ArrayItemSyntax arrayItem: return(GetTypeInfoInternal(context, arrayItem.Value)); case BinaryOperationSyntax binary: return(GetBinaryOperationType(context, binary)); case FunctionArgumentSyntax functionArgument: return(GetTypeInfoInternal(context, functionArgument.Expression)); case FunctionCallSyntax functionCall: return(GetFunctionCallType(context, functionCall)); case NullLiteralSyntax _: // null is its own type return(LanguageConstants.Null); case ParenthesizedExpressionSyntax parenthesized: // parentheses don't change the type of the parenthesized expression return(GetTypeInfoInternal(context, parenthesized.Expression)); case PropertyAccessSyntax propertyAccess: return(GetPropertyAccessType(context, propertyAccess)); case ArrayAccessSyntax arrayAccess: return(GetArrayAccessType(context, arrayAccess)); case TernaryOperationSyntax ternary: return(GetTernaryOperationType(context, ternary)); case UnaryOperationSyntax unary: return(GetUnaryOperationType(context, unary)); case VariableAccessSyntax variableAccess: return(GetVariableAccessType(context, variableAccess)); case SkippedTriviaSyntax _: // error should have already been raised by the ParseDiagnosticsVisitor - no need to add another return(new ErrorTypeSymbol(Enumerable.Empty <ErrorDiagnostic>())); default: return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax).InvalidExpression())); } } if (this.typeCheckCache.TryGetValue(syntax, out var cachedType)) { // the result was already in our cache return(cachedType); } var type = GetTypeInfoWithoutCache(); this.typeCheckCache.TryAdd(syntax, type); return(type); }
private TypeSymbol GetArrayAccessType(TypeManagerContext context, ArrayAccessSyntax syntax) { var errors = new List <ErrorDiagnostic>(); var baseType = this.GetTypeInfoInternal(context, syntax.BaseExpression); CollectErrors(errors, baseType); var indexType = this.GetTypeInfoInternal(context, syntax.IndexExpression); CollectErrors(errors, indexType); if (errors.Any() || indexType.TypeKind == TypeKind.Error) { return(new ErrorTypeSymbol(errors)); } if (baseType.TypeKind == TypeKind.Any) { // base expression is of type any if (indexType.TypeKind == TypeKind.Any) { // index is also of type any return(LanguageConstants.Any); } if (TypeValidator.AreTypesAssignable(indexType, LanguageConstants.Int) == true || TypeValidator.AreTypesAssignable(indexType, LanguageConstants.String) == true) { // index expression is string | int but base is any return(LanguageConstants.Any); } // index was of the wrong type return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.IndexExpression).StringOrIntegerIndexerRequired(indexType))); } if (baseType is ArrayType baseArray) { // we are indexing over an array if (TypeValidator.AreTypesAssignable(indexType, LanguageConstants.Int) == true) { // the index is of "any" type or integer type // return the item type return(baseArray.ItemType); } return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.IndexExpression).ArraysRequireIntegerIndex(indexType))); } if (baseType is ObjectType baseObject) { // we are indexing over an object if (indexType.TypeKind == TypeKind.Any) { // index is of type "any" return(GetExpressionedPropertyType(baseObject, syntax.IndexExpression)); } if (TypeValidator.AreTypesAssignable(indexType, LanguageConstants.String) == true) { switch (syntax.IndexExpression) { case StringSyntax @string when @string.IsInterpolated() == false: var propertyName = @string.GetLiteralValue(); return(this.GetNamedPropertyType(baseObject, syntax.IndexExpression, propertyName)); default: // the property name is itself an expression return(this.GetExpressionedPropertyType(baseObject, syntax.IndexExpression)); } } return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.IndexExpression).ObjectsRequireStringIndex(indexType))); } // index was of the wrong type return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.BaseExpression).IndexerRequiresObjectOrArray(baseType))); }
public TypeSymbol GetTypeInfo(SyntaxBase syntax, TypeManagerContext context) => GetTypeInfoInternal(context, syntax);