public override void VisitResourceDeclarationSyntax(ResourceDeclarationSyntax syntax) => AssignTypeWithCaching(syntax, () => { var stringSyntax = syntax.TryGetType(); if (stringSyntax != null && stringSyntax.IsInterpolated()) { // TODO: in the future, we can relax this check to allow interpolation with compile-time constants. // right now, codegen will still generate a format string however, which will cause problems for the type. return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).ResourceTypeInterpolationUnsupported())); } var stringContent = stringSyntax?.GetLiteralValue(); if (stringContent == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidResourceType())); } // TODO: This needs proper namespace, type, and version resolution logic in the future var typeReference = ResourceTypeReference.TryParse(stringContent); if (typeReference == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidResourceType())); } // TODO: Construct/lookup type information based on JSON schema or swagger // for now assuming very basic resource schema return(new ResourceType(stringContent, LanguageConstants.CreateResourceProperties(typeReference), additionalProperties: null, typeReference)); });
private IEnumerable <Diagnostic> ValidateIdentifierAccess(ParameterDeclarationSyntax syntax) { return(SyntaxAggregator.Aggregate(syntax, new List <Diagnostic>(), (accumulated, current) => { if (current is VariableAccessSyntax) { var symbol = bindings[current]; // Error: already has error info attached, no need to add more // Parameter: references are permitted in other parameters' default values as long as there is not a cycle (BCP080) // Function: we already validate that a function cannot be used as a variable (BCP063) // Output: we already validate that outputs cannot be referenced in expressions (BCP058) if (symbol.Kind != SymbolKind.Error && symbol.Kind != SymbolKind.Parameter && symbol.Kind != SymbolKind.Function && symbol.Kind != SymbolKind.Output) { accumulated.Add(DiagnosticBuilder.ForPosition(current).CannotReferenceSymbolInParamDefaultValue()); } } return accumulated; }, accumulated => accumulated)); }
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 DeclaredTypeAssignment?GetResourceAccessType(ResourceAccessSyntax syntax) { if (!syntax.ResourceName.IsValid) { return(null); } // We should already have a symbol, use its type. var symbol = this.binder.GetSymbolInfo(syntax); if (symbol == null) { throw new InvalidOperationException("ResourceAccessSyntax was not assigned a symbol during name binding."); } if (symbol is ErrorSymbol error) { return(new DeclaredTypeAssignment(ErrorType.Create(error.GetDiagnostics()), syntax)); } else if (symbol is not ResourceSymbol resourceSymbol) { var baseType = GetDeclaredType(syntax.BaseExpression); var typeString = baseType?.Kind.ToString() ?? LanguageConstants.ErrorName; return(new DeclaredTypeAssignment(ErrorType.Create(DiagnosticBuilder.ForPosition(syntax.ResourceName).ResourceRequiredForResourceAccess(typeString)), syntax)); }
public override void VisitResourceDeclarationSyntax(ResourceDeclarationSyntax syntax) => AssignTypeWithDiagnostics(syntax, diagnostics => { var stringSyntax = syntax.TryGetType(); if (stringSyntax != null && stringSyntax.IsInterpolated()) { // TODO: in the future, we can relax this check to allow interpolation with compile-time constants. // right now, codegen will still generate a format string however, which will cause problems for the type. return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).ResourceTypeInterpolationUnsupported())); } var stringContent = stringSyntax?.TryGetLiteralValue(); if (stringContent == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidResourceType())); } // TODO: This needs proper namespace, type, and version resolution logic in the future var typeReference = ResourceTypeReference.TryParse(stringContent); if (typeReference == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidResourceType())); } var declaredType = resourceTypeRegistrar.GetType(typeReference); return(TypeValidator.NarrowTypeAndCollectDiagnostics(typeManager, syntax.Body, declaredType, diagnostics)); });
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 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)); }
public override void VisitBinaryOperationSyntax(BinaryOperationSyntax syntax) => AssignTypeWithCaching(syntax, () => { var errors = new List <ErrorDiagnostic>(); var operandType1 = VisitAndReturnType(syntax.LeftExpression); CollectErrors(errors, operandType1); var operandType2 = VisitAndReturnType(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))); });
protected void FlagDeployTimeConstantViolation(SyntaxBase errorSyntax, DeclaredSymbol?accessedSymbol = null, ObjectType?accessedObjectType = null, IEnumerable <string>?variableDependencyChain = null) { var accessedSymbolName = accessedSymbol?.Name; var accessiblePropertyNames = GetAccessiblePropertyNames(accessedSymbol, accessedObjectType); var containerObjectSyntax = this.DeployTimeConstantContainer is ObjectPropertySyntax ? this.SemanticModel.Binder.GetParent(this.DeployTimeConstantContainer) as ObjectSyntax : null; var containerObjectType = containerObjectSyntax is not null ? this.SemanticModel.TypeManager.GetDeclaredType(containerObjectSyntax) : null; var diagnosticBuilder = DiagnosticBuilder.ForPosition(errorSyntax); var diagnostic = this.DeployTimeConstantContainer switch { ObjectPropertySyntax propertySyntax when propertySyntax.TryGetKeyText() is { } propertyName => diagnosticBuilder.RuntimeValueNotAllowedInProperty(propertyName, containerObjectType?.Name, accessedSymbolName, accessiblePropertyNames, variableDependencyChain), IfConditionSyntax => diagnosticBuilder.RuntimeValueNotAllowedInIfConditionExpression(accessedSymbolName, accessiblePropertyNames, variableDependencyChain), // Corner case: the runtime value is in the for-body of a variable declaration. ForSyntax forSyntax when ErrorSyntaxInForBodyOfVariable(forSyntax, errorSyntax) is string variableName => diagnosticBuilder.RuntimeValueNotAllowedInVariableForBody(variableName, accessedSymbolName, accessiblePropertyNames, variableDependencyChain), ForSyntax => diagnosticBuilder.RuntimeValueNotAllowedInForExpression(accessedSymbolName, accessiblePropertyNames, variableDependencyChain), FunctionCallSyntaxBase functionCallSyntaxBase => diagnosticBuilder.RuntimeValueNotAllowedInRunTimeFunctionArguments(functionCallSyntaxBase.Name.IdentifierName, accessedSymbolName, accessiblePropertyNames, variableDependencyChain), _ => throw new ArgumentOutOfRangeException(nameof(this.DeployTimeConstantContainer), "Expected an ObjectPropertySyntax with a propertyName, a IfConditionSyntax, a ForSyntax, or a FunctionCallSyntaxBase."), }; this.DiagnosticWriter.Write(diagnostic); }
public Task <BicepRegistryCacheResponse> Handle(BicepRegistryCacheParams request, CancellationToken cancellationToken) { // If any of the following paths result in an exception being thrown (and surfaced client-side to the user), // it indicates a code defect client or server-side. // In normal operation, the user should never see them regardless of how malformed their code is. // TODO: Add documentUri to BicepRegistryCacheParams and get the config for the documentUri. var configuration = this.configurationManager.GetBuiltInConfiguration(); var moduleReference = this.moduleDispatcher.TryGetModuleReference(request.Target, configuration, out _) ?? throw new InvalidOperationException($"The client specified an invalid module reference '{request.Target}'."); if (!moduleReference.IsExternal) { throw new InvalidOperationException($"The specified module reference '{request.Target}' refers to a local module which is not supported by {BicepCacheLspMethod} requests."); } if (this.moduleDispatcher.GetModuleRestoreStatus(moduleReference, out _) != ModuleRestoreStatus.Succeeded) { throw new InvalidOperationException($"The module '{moduleReference.FullyQualifiedReference}' has not yet been successfully restored."); } var uri = this.moduleDispatcher.TryGetLocalModuleEntryPointUri(null, moduleReference, out _) ?? throw new InvalidOperationException($"Unable to obtain the entry point URI for module '{moduleReference.FullyQualifiedReference}'."); if (!this.fileResolver.TryRead(uri, out var contents, out var failureBuilder)) { var message = failureBuilder(DiagnosticBuilder.ForPosition(new TextSpan(0, 0))).Message; throw new InvalidOperationException($"Unable to read file '{uri}'. {message}"); } return(Task.FromResult(new BicepRegistryCacheResponse(contents))); }
public override void VisitTernaryOperationSyntax(TernaryOperationSyntax syntax) => AssignTypeWithCaching(syntax, () => { var errors = new List <ErrorDiagnostic>(); // ternary operator requires the condition to be of bool type var conditionType = VisitAndReturnType(syntax.ConditionExpression); CollectErrors(errors, conditionType); var trueType = VisitAndReturnType(syntax.TrueExpression); CollectErrors(errors, trueType); var falseType = VisitAndReturnType(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?CheckForCyclicError(SyntaxBase syntax) { if (this.binder.GetSymbolInfo(syntax) is not DeclaredSymbol declaredSymbol) { return(null); } if (declaredSymbol.DeclaringSyntax == syntax) { // Report cycle errors on accesses to cyclic symbols, not on the declaration itself return(null); } if (this.binder.TryGetCycle(declaredSymbol) is { } cycle) { // there's a cycle. stop visiting now or we never will! if (cycle.Length == 1) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).CyclicExpressionSelfReference())); } return(ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).CyclicExpression(cycle.Select(x => x.Name)))); } return(null); }
public TypeSymbol GetAssignedType(ITypeManager typeManager, ArraySyntax?allowedSyntax) { var assignedType = this.GetDeclaredType(); // TODO: remove SyntaxHelper.TryGetAllowedSyntax when we drop parameter modifiers support. if (allowedSyntax is not null && !allowedSyntax.Items.Any()) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(allowedSyntax).AllowedMustContainItems())); } var allowedItemTypes = allowedSyntax?.Items.Select(typeManager.GetTypeInfo); if (ReferenceEquals(assignedType, LanguageConstants.String)) { if (allowedItemTypes?.All(itemType => itemType is StringLiteralType) == true) { assignedType = UnionType.Create(allowedItemTypes); } else { // In order to support assignment for a generic string to enum-typed properties (which generally is forbidden), // we need to relax the validation for string parameters without 'allowed' values specified. assignedType = LanguageConstants.LooseString; } } if (ReferenceEquals(assignedType, LanguageConstants.Array) && allowedItemTypes?.All(itemType => itemType is StringLiteralType) == true) { assignedType = new TypedArrayType(UnionType.Create(allowedItemTypes), TypeSymbolValidationFlags.Default); } return(assignedType); }
private Symbol LookupGlobalSymbolByName(IdentifierSyntax identifierSyntax, bool isFunctionCall) { // attempt to find name in the built in namespaces. imported namespaces will be present in the declarations list as they create declared symbols. if (this.namespaceResolver.BuiltIns.TryGetValue(identifierSyntax.IdentifierName) is { } namespaceSymbol) { // namespace symbol found return(namespaceSymbol); } // declarations must not have a namespace value, namespaces are used to fully qualify a function access. // There might be instances where a variable declaration for example uses the same name as one of the imported // functions, in this case to differentiate a variable declaration vs a function access we check the namespace value, // the former case must have an empty namespace value whereas the latter will have a namespace value. if (this.declarations.TryGetValue(identifierSyntax.IdentifierName, out var globalSymbol)) { // we found the symbol in the global namespace return(globalSymbol); } // attempt to find function in all imported namespaces var foundSymbols = namespaceResolver.ResolveUnqualifiedFunction(identifierSyntax, includeDecorators: allowedFlags.HasAnyDecoratorFlag()); if (foundSymbols.Count() > 1) { // ambiguous symbol return(new ErrorSymbol(DiagnosticBuilder.ForPosition(identifierSyntax).AmbiguousSymbolReference(identifierSyntax.IdentifierName, namespaceResolver.GetNamespaceNames().ToImmutableSortedSet(StringComparer.Ordinal)))); } var foundSymbol = foundSymbols.FirstOrDefault(); return(isFunctionCall ? SymbolValidator.ResolveUnqualifiedFunction(allowedFlags, foundSymbol, identifierSyntax, namespaceResolver) : SymbolValidator.ResolveUnqualifiedSymbol(foundSymbol, identifierSyntax, namespaceResolver, declarations.Keys)); }
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 Symbol LookupSymbolByName(string name, TextSpan span) { if (this.declarations.TryGetValue(name, out var localSymbol)) { // we found the symbol in the local namespace return(ValidateFunctionFlags(localSymbol, span)); } // symbol does not exist in the local namespace // try it in the imported namespaces // match in one of the namespaces var foundSymbols = this.namespaces .Select(ns => ns.TryGetFunctionSymbol(name)) .Where(symbol => symbol != null) .ToList(); if (foundSymbols.Count() > 1) { // ambiguous symbol return(new ErrorSymbol(DiagnosticBuilder.ForPosition(span).AmbiguousSymbolReference(name, this.namespaces.Select(ns => ns.Name)))); } var foundSymbol = foundSymbols.FirstOrDefault(); if (foundSymbol == null) { return(new ErrorSymbol(DiagnosticBuilder.ForPosition(span).SymbolicNameDoesNotExist(name))); } return(ValidateFunctionFlags(foundSymbol, span)); }
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 static IEnumerable <Diagnostic> DetectDuplicateNames(SemanticModel semanticModel, ImmutableDictionary <ResourceSymbol, ScopeHelper.ScopeData> resourceScopeData, ImmutableDictionary <ModuleSymbol, ScopeHelper.ScopeData> moduleScopeData) { // This method only checks, if in one deployment we do not have 2 or more resources with this same name in one deployment to avoid template validation error // This will not check resource constraints such as necessity of having unique virtual network names within resource group var duplicateResources = GetResourceDefinitions(semanticModel, resourceScopeData) .GroupBy(x => x, ResourceDefinition.EqualityComparer) .Where(group => group.Count() > 1); foreach (var duplicatedResourceGroup in duplicateResources) { var duplicatedResourceNames = duplicatedResourceGroup.Select(x => x.ResourceName).ToArray(); foreach (var duplicatedResource in duplicatedResourceGroup) { yield return(DiagnosticBuilder.ForPosition(duplicatedResource.ResourceNamePropertyValue).ResourceMultipleDeclarations(duplicatedResourceNames)); } } var duplicateModules = GetModuleDefinitions(semanticModel, moduleScopeData) .GroupBy(x => x, ModuleDefinition.EqualityComparer) .Where(group => group.Count() > 1); foreach (var duplicatedModuleGroup in duplicateModules) { var duplicatedModuleNames = duplicatedModuleGroup.Select(x => x.ModuleName).ToArray(); foreach (var duplicatedModule in duplicatedModuleGroup) { yield return(DiagnosticBuilder.ForPosition(duplicatedModule.ModulePropertyNameValue).ModuleMultipleDeclarations(duplicatedModuleNames)); } } }
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))); }
/// <summary> /// Gets the type of the property whose name we can obtain at compile-time. /// </summary> /// <param name="baseType">The base object type</param> /// <param name="propertyExpressionPositionable">The position of the property name expression</param> /// <param name="propertyName">The resolved property name</param> private TypeSymbol GetNamedPropertyType(ObjectType baseType, IPositionable propertyExpressionPositionable, string propertyName) { if (baseType.TypeKind == TypeKind.Any) { // all properties of "any" type are of type "any" return(LanguageConstants.Any); } // is there a declared property with this name var declaredProperty = baseType.Properties.TryGetValue(propertyName); if (declaredProperty != null) { if (declaredProperty.Flags.HasFlag(TypePropertyFlags.WriteOnly)) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(propertyExpressionPositionable).WriteOnlyProperty(baseType, propertyName))); } // there is - return its type return(declaredProperty.Type); } // the property is not declared // check additional properties if (baseType.AdditionalPropertiesType != null) { // yes - return the additional property type return(baseType.AdditionalPropertiesType); } return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(propertyExpressionPositionable).UnknownProperty(baseType, propertyName))); }
public override void VisitResourceDeclarationSyntax(ResourceDeclarationSyntax syntax) { // This check is separate from IsLoopAllowedHere because this is about the appearance of a // nested resource **inside** a loop. if (this.semanticModel.Binder.GetNearestAncestor <ForSyntax>(syntax) is ForSyntax) { this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax.Span).NestedResourceNotAllowedInLoop()); } // Resources can be nested, support recursion of resource declarations var previousLoopCapableTopLevelDeclaration = this.activeLoopCapableTopLevelDeclaration; this.activeLoopCapableTopLevelDeclaration = syntax; // stash the body (handles loops and conditions as well) var previousDependsOnProperty = this.currentDependsOnProperty; this.currentDependsOnProperty = TryGetDependsOnProperty(syntax.TryGetBody()); base.VisitResourceDeclarationSyntax(syntax); // restore state this.currentDependsOnProperty = previousDependsOnProperty; this.activeLoopCapableTopLevelDeclaration = previousLoopCapableTopLevelDeclaration; }
public override void VisitForSyntax(ForSyntax syntax) { // save previous property loop count on the call stack var previousPropertyLoopCount = this.propertyLoopCount; switch (this.IsLoopAllowedHere(syntax)) { case false: // this loop was used incorrectly this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax.ForKeyword).ForExpressionsNotSupportedHere()); break; case null: // this is a property loop this.propertyLoopCount += 1; if (this.propertyLoopCount > MaximumNestedPropertyLoopCount) { // too many property loops this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax.ForKeyword).TooManyPropertyForExpressions()); } break; } // visit children base.VisitForSyntax(syntax); // restore previous property loop count this.propertyLoopCount = previousPropertyLoopCount; }
public void DiagnosticBuilder_CodesAreUnique() { var diagnosticMethods = typeof(DiagnosticBuilder.DiagnosticBuilderInternal) .GetMethods(BindingFlags.Instance | BindingFlags.Public) .Where(m => typeof(Diagnostic).IsAssignableFrom(m.ReturnType)); // verify the above Linq is actually working diagnosticMethods.Should().HaveCountGreaterThan(40); var builder = DiagnosticBuilder.ForPosition(new TextSpan(0, 10)); var definedCodes = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (var diagnosticMethod in diagnosticMethods) { var mockParams = diagnosticMethod.GetParameters().Select(CreateMockParameter); var diagnostic = diagnosticMethod.Invoke(builder, mockParams.ToArray()) as Diagnostic; if (mockParams.Any()) { // verify that all the params are actually being written in the message diagnostic !.Message.Should().ContainAll(CollectExpectedStrings(mockParams), $"method {diagnosticMethod.Name} should use all of its parameters in the format string."); } // verify that the Code is unique definedCodes.Should().NotContain(diagnostic !.Code, $"Method {diagnosticMethod.Name} should be assigned a unique error code."); definedCodes.Add(diagnostic !.Code); } }
private void ValidateScope(ILanguageScope scope) { // collect duplicate identifiers at this scope // declaring a variable in a local scope hides the parent scope variables, // so we don't need to look at other levels var outputDeclarations = scope.Declarations.Where(decl => decl is OutputSymbol); var nonOutputDeclarations = scope.Declarations.Where(decl => decl is not OutputSymbol); // all symbols apart from outputs are in the same namespace, so check for uniqueness. this.Diagnostics.AddRange( FindDuplicateNamedSymbols(nonOutputDeclarations) .Select(decl => DiagnosticBuilder.ForPosition(decl.NameSyntax).IdentifierMultipleDeclarations(decl.Name))); // output symbols cannot be referenced, so the names declared by them do not need to be unique in the scope. // we still need to ensure that they unique among other outputs. this.Diagnostics.AddRange( FindDuplicateNamedSymbols(outputDeclarations) .Select(decl => DiagnosticBuilder.ForPosition(decl.NameSyntax).OutputMultipleDeclarations(decl.Name))); // imported namespaces are reserved in all the scopes // otherwise the user could accidentally hide a namespace which would remove the ability // to fully qualify a function this.Diagnostics.AddRange(nonOutputDeclarations .Where(decl => decl.NameSyntax.IsValid && this.importedNamespaces.ContainsKey(decl.Name)) .Select(reservedSymbol => DiagnosticBuilder.ForPosition(reservedSymbol.NameSyntax).SymbolicNameCannotUseReservedNamespaceName(reservedSymbol.Name, this.importedNamespaces.Keys))); }
public TypeSymbol GetDeclaredType(ResourceScope targetScope, IResourceTypeProvider resourceTypeProvider) { var stringSyntax = this.TypeString; if (stringSyntax != null && stringSyntax.IsInterpolated()) { // TODO: in the future, we can relax this check to allow interpolation with compile-time constants. // right now, codegen will still generate a format string however, which will cause problems for the type. return(ErrorType.Create(DiagnosticBuilder.ForPosition(this.Type).ResourceTypeInterpolationUnsupported())); } var stringContent = stringSyntax?.TryGetLiteralValue(); if (stringContent == null) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(this.Type).InvalidResourceType())); } var typeReference = ResourceTypeReference.TryParse(stringContent); if (typeReference == null) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(this.Type).InvalidResourceType())); } return(resourceTypeProvider.GetType(targetScope, typeReference)); }
public TypeSymbol GetAssignedType(ITypeManager typeManager) { var assignedType = this.GetDeclaredType(); var allowedSyntax = SyntaxHelper.TryGetAllowedSyntax(this); if (allowedSyntax != null && !allowedSyntax.Items.Any()) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(allowedSyntax).AllowedMustContainItems())); } if (object.ReferenceEquals(assignedType, LanguageConstants.String)) { var allowedItemTypes = allowedSyntax?.Items.Select(typeManager.GetTypeInfo); if (allowedItemTypes != null && allowedItemTypes.All(itemType => itemType is StringLiteralType)) { assignedType = UnionType.Create(allowedItemTypes); } else { // In order to support assignment for a generic string to enum-typed properties (which generally is forbidden), // we need to relax the validation for string parameters without 'allowed' values specified. assignedType = LanguageConstants.LooseString; } } return(assignedType); }
private void AppendError() { if (this.errorSyntax == null) { throw new NullReferenceException($"{nameof(this.errorSyntax)} is null in {this.GetType().Name}"); } if (this.currentProperty == null) { throw new NullReferenceException($"{nameof(this.currentProperty)} is null in {this.GetType().Name} for syntax {this.errorSyntax.ToString()}"); } if (this.bodyObj == null) { throw new NullReferenceException($"{nameof(this.bodyObj)} is null in {this.GetType().Name} for syntax {this.errorSyntax.ToString()}"); } if (this.referencedBodyObj == null) { throw new NullReferenceException($"{nameof(this.referencedBodyObj)} is null in {this.GetType().Name} for syntax {this.errorSyntax.ToString()}"); } if (this.accessedSymbol == null) { throw new NullReferenceException($"{nameof(this.accessedSymbol)} is null in {this.GetType().Name} for syntax {this.errorSyntax.ToString()}"); } var usableKeys = this.referencedBodyObj.Properties.Where(kv => kv.Value.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant)).Select(kv => kv.Key); this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(this.errorSyntax).RuntimePropertyNotAllowed(this.currentProperty, usableKeys, this.accessedSymbol, this.variableVisitorStack?.ToArray().Reverse())); this.errorSyntax = null; this.referencedBodyObj = null; this.accessedSymbol = null; this.variableVisitorStack = null; }
private TypeSymbol?CheckForCyclicError(SyntaxBase syntax) { if (!(bindings.TryGetValue(syntax, out var symbol) && (symbol is DeclaredSymbol declaredSymbol))) { return(null); } if (declaredSymbol.DeclaringSyntax == syntax) { // Report cycle errors on accesses to cyclic symbols, not on the declaration itself return(null); } if (cyclesBySymbol.TryGetValue(declaredSymbol, out var cycle)) { // there's a cycle. stop visiting now or we never will! if (cycle.Length == 1) { return(ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).CyclicExpressionSelfReference())); } return(ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).CyclicExpression(cycle.Select(x => x.Name)))); } return(null); }
static DecoratorValidator ValidateTargetType(TypeSymbol attachableType) => (decoratorName, decoratorSyntax, targetType, _, diagnosticWriter) => { if (!TypeValidator.AreTypesAssignable(targetType, attachableType)) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).CannotAttacheDecoratorToTarget(decoratorName, attachableType, targetType)); } };
public override void VisitIdentifierSyntax(IdentifierSyntax syntax) { if (syntax.IdentifierName.Length > LanguageConstants.MaxIdentifierLength) { this.diagnostics.Add(DiagnosticBuilder.ForPosition(syntax.Identifier).IdentifierNameExceedsLimit()); } base.VisitIdentifierSyntax(syntax); }