public void SymbolicNameDoesNotExistWithSuggestion_ProducesPreferredNameReplacementCodeFix() { var builder = DiagnosticBuilder.ForPosition(new TextSpan(0, 10)); var diagnostic = builder.SymbolicNameDoesNotExistWithSuggestion("hellO", "hello"); diagnostic.Fixes.Should().NotBeNull(); diagnostic.Fixes.Should().HaveCount(1); var fix = diagnostic.Fixes.First(); fix.IsPreferred.Should().BeTrue(); fix.Replacements.Should().NotBeNull(); fix.Replacements.Should().HaveCount(1); var replacement = fix.Replacements.First(); replacement.Span.Should().Be(diagnostic.Span); replacement.Text.Should().Be("hello"); }
public void UnknownPropertyWithSuggestion_ProducesPreferredPropertyReplacementCodeFix() { var builder = DiagnosticBuilder.ForPosition(new TextSpan(0, 10)); var diagnostic = builder.UnknownPropertyWithSuggestion(false, new PrimitiveType("testType", TypeSymbolValidationFlags.Default), "networkACLs", "networkAcls"); diagnostic.Fixes.Should().NotBeNull(); diagnostic.Fixes.Should().HaveCount(1); var fix = diagnostic.Fixes.First(); fix.IsPreferred.Should().BeTrue(); fix.Replacements.Should().NotBeNull(); fix.Replacements.Should().HaveCount(1); var replacement = fix.Replacements.First(); replacement.Span.Should().Be(diagnostic.Span); replacement.Text.Should().Be("networkAcls"); }
public void Validate(DecoratorSyntax decoratorSyntax, TypeSymbol targetType, ITypeManager typeManager, IDiagnosticWriter diagnosticWriter) { if (targetType is ErrorType) { return; } if (this.validator != null) { this.validator.Invoke(this.Overload.Name, decoratorSyntax, targetType, typeManager, diagnosticWriter); return; } // No custom validator provided. Just validate the target type. if (!this.CanAttachTo(targetType)) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(decoratorSyntax).CannotAttacheDecoratorToTarget(this.Overload.Name, attachableType, targetType)); } }
protected override void VisitInternal(SyntaxBase node) { var kind = GetExpressionKind(node); var newState = GetNewState(kind); if (newState == VisitorState.SecondOperatorChainInsideObjectLiteralInsideFirstOperatorChain) { this.diagnostics.Add(DiagnosticBuilder.ForPosition(node.Span).ExpressionContainsObjectLiteralContainingOtherExpressions()); // we won't suddenly regain the ability to compile the expression by visiting children, // so let's not continue deeper into the tree return; } this.stack.Push(newState); base.VisitInternal(node); this.stack.Pop(); }
public void ShouldReturnSubstitutionsInString() { Diagnostics diags; string schemaName = $"{ResourceLocationPrefix}.Docs.DiagnosticSchema.xml"; using (Stream s = typeof(DependencyInjector).Assembly.GetManifestResourceStream(schemaName)) { DiagnosticBuilder db = new DiagnosticBuilder(s); diags = db.Diagnostics; dynamic diag = diags.Groups["InvalidBean"].CreateDiagnostic(); diag.AbstractOrStaticClass = "testBaseClassType"; diag.ClassMode = "MyClassMode"; diags.Groups["InvalidBean"].Add(diag); } string str = diags.ToString(); Assert.IsTrue(str.Contains("testBaseClassType")); }
public override void VisitInstanceFunctionCallSyntax(InstanceFunctionCallSyntax syntax) { base.VisitInstanceFunctionCallSyntax(syntax); Symbol foundSymbol; // baseExpression must be bound to a namespaceSymbol otherwise there was an error if (bindings.ContainsKey(syntax.BaseExpression) && bindings[syntax.BaseExpression] is NamespaceSymbol namespaceSymbol) { foundSymbol = this.LookupSymbolByName(syntax.Name.IdentifierName, syntax.Name.Span, namespaceSymbol); } else { foundSymbol = new UnassignableSymbol(DiagnosticBuilder.ForPosition(syntax.Name.Span).SymbolicNameDoesNotExist(syntax.Name.IdentifierName)); } // bind what we got - the type checker will validate if it fits this.bindings.Add(syntax, foundSymbol); }
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) { var nameCandidates = this.declarations.Values .Concat(this.namespaces.SelectMany(ns => ns.Descendants)) .Select(symbol => symbol.Name) .ToImmutableSortedSet(); var suggestedName = SpellChecker.GetSpellingSuggestion(name, nameCandidates); return(suggestedName != null ? new ErrorSymbol(DiagnosticBuilder.ForPosition(span).SymbolicNameDoesNotExistWithSuggestion(name, suggestedName)) : new ErrorSymbol(DiagnosticBuilder.ForPosition(span).SymbolicNameDoesNotExist(name))); } return(ValidateFunctionFlags(foundSymbol, span)); }
public override void VisitParameterDeclarationSyntax(ParameterDeclarationSyntax syntax) => AssignTypeWithDiagnostics(syntax, diagnostics => { diagnostics.AddRange(this.ValidateIdentifierAccess(syntax)); // assume "any" type when the parameter has parse errors (either missing or was skipped) var declaredType = syntax.ParameterType == null ? LanguageConstants.Any : LanguageConstants.TryGetDeclarationType(syntax.ParameterType.TypeName); if (declaredType == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidParameterType())); } var assignedType = declaredType; if (object.ReferenceEquals(assignedType, LanguageConstants.String)) { var allowedItemTypes = SyntaxHelper.TryGetAllowedItems(syntax)? .Select(item => typeManager.GetTypeInfo(item)); if (allowedItemTypes != null && allowedItemTypes.All(itemType => itemType is StringLiteralType)) { assignedType = UnionType.Create(allowedItemTypes); } } switch (syntax.Modifier) { case ParameterDefaultValueSyntax defaultValueSyntax: diagnostics.AddRange(ValidateDefaultValue(defaultValueSyntax, assignedType)); break; case ObjectSyntax modifierSyntax: var modifierType = LanguageConstants.CreateParameterModifierType(declaredType, assignedType); // we don't need to actually use the narrowed type; just need to use this to collect assignment diagnostics TypeValidator.NarrowTypeAndCollectDiagnostics(typeManager, modifierSyntax, modifierType, diagnostics); break; } return(assignedType); });
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 true when this.activeLoopCapableTopLevelDeclaration is VariableDeclarationSyntax variable && InlineDependencyVisitor.ShouldInlineVariable(this.semanticModel, variable, out var variableChain): // this is a loop variable that has a dependency on functions that are not supported in JSON variables // we are initially blocking this because not all cases can be generated in the JSON // unable to get a detailed variable dependency chain // log a generic error instead and put it on the "for" keyword this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax.ForKeyword).VariableLoopsRuntimeDependencyNotAllowed(variableChain)); 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; }
private Symbol ValidateFunctionFlags(Symbol symbol, TextSpan span) { if (!(symbol is FunctionSymbol functionSymbol)) { return(symbol); } var functionFlags = functionSymbol.Overloads.Select(overload => overload.Flags).Aggregate((x, y) => x | y); if (functionFlags.HasFlag(FunctionFlags.ParamDefaultsOnly) && !allowedFlags.HasFlag(FunctionFlags.ParamDefaultsOnly)) { return(new ErrorSymbol(DiagnosticBuilder.ForPosition(span).FunctionOnlyValidInParameterDefaults(functionSymbol.Name))); } if (functionFlags.HasFlag(FunctionFlags.RequiresInlining) && !allowedFlags.HasFlag(FunctionFlags.RequiresInlining)) { return(new ErrorSymbol(DiagnosticBuilder.ForPosition(span).FunctionOnlyValidInResourceBody(functionSymbol.Name))); } return(symbol); }
private static void GetArrayAssignmentDiagnostics(ITypeManager typeManager, ArraySyntax expression, ArrayType targetType, IList <Diagnostic> diagnostics, bool skipConstantCheck) { // if we have parse errors, no need to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return; } foreach (var arrayItemSyntax in expression.Items) { GetExpressionAssignmentDiagnosticsInternal( typeManager, arrayItemSyntax.Value, targetType.Item.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).ArrayTypeMismatch(expectedType.Name, actualType.Name), skipConstantCheck, skipTypeErrors: true); } }
public TypeSymbol GetAssignedType(ITypeManager typeManager, ArraySyntax?allowedSyntax) { var assignedType = typeManager.GetDeclaredType(this); if (assignedType is null) { // We don't expect this to happen for a parameter. return(ErrorType.Empty()); } // 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 = TypeHelper.CreateTypeUnion(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(TypeHelper.CreateTypeUnion(allowedItemTypes), TypeSymbolValidationFlags.Default); } return(assignedType); }
private SyntaxTreeGrouping Build(Uri entryFileUri) { var entryPoint = PopulateRecursive(entryFileUri, out var entryPointLoadFailureBuilder); if (entryPoint == null) { // TODO: If we upgrade to netstandard2.1, we should be able to use the following to hint to the compiler that failureBuilder is non-null: // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis var failureBuilder = entryPointLoadFailureBuilder ?? throw new InvalidOperationException($"Expected {nameof(PopulateRecursive)} to provide failure diagnostics"); var diagnostic = failureBuilder(DiagnosticBuilder.ForPosition(new TextSpan(0, 0))); throw new ErrorDiagnosticException(diagnostic); } ReportFailuresForCycles(); return(new SyntaxTreeGrouping( entryPoint, syntaxTrees.Values.OfType <SyntaxTree>().ToImmutableHashSet(), moduleLookup.ToImmutableDictionary(), moduleFailureLookup.ToImmutableDictionary())); }
private TypeSymbol?CheckForCyclicError(SyntaxBase syntax) { if (cyclesBySyntax.TryGetValue(syntax, out var cycle)) { // there's a cycle. stop visiting now or we never will! if (cycle.Length == 1) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax).CyclicSelfReference())); } var syntaxBinding = bindings[syntax]; // show the cycle as originating from the current syntax symbol var cycleSuffix = cycle.TakeWhile(x => x != syntaxBinding); var cyclePrefix = cycle.Skip(cycleSuffix.Count()); var orderedCycle = cyclePrefix.Concat(cycleSuffix); return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax).CyclicExpression(orderedCycle.Select(x => x.Name)))); } return(null); }
private Symbol LookupSymbolByName(IdentifierSyntax identifierSyntax, bool isFunctionCall) { // attempt to find name in the imported namespaces if (this.namespaces.TryGetValue(identifierSyntax.IdentifierName, out var 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 localSymbol)) { // we found the symbol in the local namespace return(localSymbol); } // attempt to find function in all imported namespaces var foundSymbols = this.namespaces .Select(kvp => allowedFlags.HasDecoratorFlag() ? kvp.Value.Type.MethodResolver.TryGetSymbol(identifierSyntax) ?? kvp.Value.Type.DecoratorResolver.TryGetSymbol(identifierSyntax) : kvp.Value.Type.MethodResolver.TryGetSymbol(identifierSyntax)) .Where(symbol => symbol != null) .ToList(); if (foundSymbols.Count > 1) { // ambiguous symbol return(new ErrorSymbol(DiagnosticBuilder.ForPosition(identifierSyntax).AmbiguousSymbolReference(identifierSyntax.IdentifierName, this.namespaces.Keys))); } var foundSymbol = foundSymbols.FirstOrDefault(); return(isFunctionCall ? SymbolValidator.ResolveUnqualifiedFunction(allowedFlags, foundSymbol, identifierSyntax, namespaces.Values) : SymbolValidator.ResolveUnqualifiedSymbol(foundSymbol, identifierSyntax, namespaces.Values, declarations.Keys)); }
private static TypeSymbol LoadTextContentTypeBuilder(IBinder binder, IFileResolver fileResolver, IDiagnosticWriter diagnostics, ImmutableArray <FunctionArgumentSyntax> arguments, ImmutableArray <TypeSymbol> argumentTypes) { if (argumentTypes[0] is not StringLiteralType filePathType) { diagnostics.Write(DiagnosticBuilder.ForPosition(arguments[0]).CompileTimeConstantRequired()); return(LanguageConstants.String); } var filePathValue = filePathType.RawStringValue; var fileUri = GetFileUriWithDiagnostics(binder, fileResolver, diagnostics, filePathValue, arguments[0]); if (fileUri is null) { return(LanguageConstants.String); } var fileEncoding = Encoding.UTF8; if (argumentTypes.Length > 1) { if (argumentTypes[1] is not StringLiteralType encodingType) { diagnostics.Write(DiagnosticBuilder.ForPosition(arguments[1]).CompileTimeConstantRequired()); return(LanguageConstants.String); } fileEncoding = LanguageConstants.SupportedEncodings.First(x => string.Equals(x.name, encodingType.RawStringValue, LanguageConstants.IdentifierComparison)).encoding; } if (!fileResolver.TryRead(fileUri, out var fileContent, out var fileReadFailureBuilder, fileEncoding, LanguageConstants.MaxLiteralCharacterLimit, out var detectedEncoding)) { diagnostics.Write(fileReadFailureBuilder.Invoke(DiagnosticBuilder.ForPosition(arguments[0]))); return(LanguageConstants.String); } if (arguments.Length > 1 && fileEncoding != detectedEncoding) { diagnostics.Write(DiagnosticBuilder.ForPosition(arguments[1]).FileEncodingMismatch(detectedEncoding.WebName)); } return(new StringLiteralType(fileContent)); }
private static TypeSymbol LoadContentAsBase64TypeBuilder(IBinder binder, IFileResolver fileResolver, IDiagnosticWriter diagnostics, ImmutableArray <FunctionArgumentSyntax> arguments, ImmutableArray <TypeSymbol> argumentTypes) { if (argumentTypes[0] is not StringLiteralType filePathType) { diagnostics.Write(DiagnosticBuilder.ForPosition(arguments[0]).CompileTimeConstantRequired()); return(LanguageConstants.String); } var filePathValue = filePathType.RawStringValue; var fileUri = GetFileUriWithDiagnostics(binder, fileResolver, diagnostics, filePathValue, arguments[0]); if (fileUri is null) { return(LanguageConstants.String); } if (!fileResolver.TryReadAsBase64(fileUri, out var fileContent, out var fileReadFailureBuilder, LanguageConstants.MaxLiteralCharacterLimit)) { diagnostics.Write(fileReadFailureBuilder.Invoke(DiagnosticBuilder.ForPosition(arguments[0]))); return(LanguageConstants.String); } return(new StringLiteralType(binder.FileSymbol.FileUri.MakeRelativeUri(fileUri).ToString(), fileContent)); }
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)))); } }
public override void VisitParameterDeclarationSyntax(ParameterDeclarationSyntax syntax) => AssignTypeWithCaching(syntax, () => { var primitiveType = LanguageConstants.TryGetDeclarationType(syntax.Type.TypeName); if (primitiveType == null) { return(new ErrorTypeSymbol(DiagnosticBuilder.ForPosition(syntax.Type).InvalidParameterType())); } if (!object.ReferenceEquals(primitiveType, LanguageConstants.String)) { return(primitiveType); } var allowedItemTypes = SyntaxHelper.TryGetAllowedItems(syntax)? .Select(item => VisitAndReturnType(item)); if (allowedItemTypes == null || !allowedItemTypes.All(itemType => itemType is StringLiteralType)) { return(primitiveType); } return(UnionType.Create(allowedItemTypes)); });
public override void VisitObjectPropertySyntax(ObjectPropertySyntax syntax) { if (syntax.TryGetKeyText() == null && syntax.Value is ForSyntax) { // block loop usage with properties whose names are expressions this.diagnosticWriter.Write(DiagnosticBuilder.ForPosition(syntax.Key).ExpressionedPropertiesNotAllowedWithLoops()); } bool insideDependsOnInThisScope = ReferenceEquals(this.currentDependsOnProperty, syntax); // set this to true if the current property is the top-level dependsOn property // leave it true if already set to true this.insideTopLevelDependsOn = this.insideTopLevelDependsOn || insideDependsOnInThisScope; // visit children base.VisitObjectPropertySyntax(syntax); // clear the flag after we leave the dependsOn property if (insideDependsOnInThisScope) { this.insideTopLevelDependsOn = false; } }
private static Uri?GetFileUriWithDiagnostics(IBinder binder, IFileResolver fileResolver, IDiagnosticWriter diagnostics, string filePath, SyntaxBase filePathArgument) { if (!LocalModuleReference.Validate(filePath, out var validateFilePathFailureBuilder)) { diagnostics.Write(validateFilePathFailureBuilder.Invoke(DiagnosticBuilder.ForPosition(filePathArgument))); return(null); } var fileUri = fileResolver.TryResolveFilePath(binder.FileSymbol.FileUri, filePath); if (fileUri is null) { diagnostics.Write(DiagnosticBuilder.ForPosition(filePathArgument).FilePathCouldNotBeResolved(filePath, binder.FileSymbol.FileUri.LocalPath)); return(null); } if (!fileUri.IsFile) { diagnostics.Write(DiagnosticBuilder.ForPosition(filePathArgument).UnableToLoadNonFileUri(fileUri)); return(null); } return(fileUri); }
private IEnumerable <ErrorDiagnostic> ValidateIdentifierAccess() { return(SyntaxAggregator.Aggregate(this.DeclaringParameter, new List <ErrorDiagnostic>(), (accumulated, current) => { if (current is VariableAccessSyntax) { Symbol?symbol = this.Context.Bindings.TryGetValue(current); // excluded symbol kinds already generate errors - no need to duplicate if (symbol != null && 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 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) { var ambiguousNamespaces = foundSymbols.OfType <FunctionSymbol>().Select(x => x.DeclaringObject.Name); // ambiguous symbol return(new ErrorSymbol(DiagnosticBuilder.ForPosition(identifierSyntax).AmbiguousSymbolReference(identifierSyntax.IdentifierName, ambiguousNamespaces.ToImmutableSortedSet(StringComparer.Ordinal)))); } var foundSymbol = foundSymbols.FirstOrDefault(); return(isFunctionCall ? SymbolValidator.ResolveUnqualifiedFunction(allowedFlags, foundSymbol, identifierSyntax, namespaceResolver) : SymbolValidator.ResolveUnqualifiedSymbol(foundSymbol, identifierSyntax, namespaceResolver, declarations.Keys)); }
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; // 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 static TypeSymbol NarrowArrayAssignmentType(ITypeManager typeManager, ArraySyntax expression, ArrayType targetType, IDiagnosticWriter diagnosticWriter, bool skipConstantCheck) { // if we have parse errors, no need to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(targetType); } var arrayProperties = new List <TypeSymbol>(); foreach (var arrayItemSyntax in expression.Items) { arrayProperties.Add(NarrowTypeInternal( typeManager, arrayItemSyntax.Value, targetType.Item.Type, diagnosticWriter, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).ArrayTypeMismatch(ShouldWarn(targetType), expectedType, actualType), skipConstantCheck, skipTypeErrors: true)); } return(new TypedArrayType(UnionType.Create(arrayProperties), targetType.ValidationFlags)); }
private static void GetObjectAssignmentDiagnostics(ITypeManager typeManager, ObjectSyntax expression, ObjectType targetType, IList <Diagnostic> diagnostics, bool skipConstantCheck) { // TODO: Short-circuit on any object to avoid unnecessary processing? // TODO: Consider doing the schema check even if there are parse errors // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return; } var propertyMap = expression.ToPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && propertyMap.ContainsKey(p.Name) == false) .Select(p => p.Name) .OrderBy(p => p) .ConcatString(LanguageConstants.ListSeparator); if (string.IsNullOrEmpty(missingRequiredProperties) == false) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperties(missingRequiredProperties)); } foreach (var declaredProperty in targetType.Properties.Values) { if (propertyMap.TryGetValue(declaredProperty.Name, out var declaredPropertySyntax)) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheck == false && declaredProperty.Flags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants diagnostics.AddRange(GetCompileTimeConstantViolation(declaredPropertySyntax.Value)); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } if (declaredProperty.Flags.HasFlag(TypePropertyFlags.ReadOnly)) { // the declared property is read-only // value cannot be assigned to a read-only property diagnostics.Add(DiagnosticBuilder.ForPosition(declaredPropertySyntax.Key).CannotAssignToReadOnlyProperty(declaredProperty.Name)); } // declared property is specified in the value object // validate type GetExpressionAssignmentDiagnosticsInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(declaredProperty.Name, expectedType, actualType), skipConstantCheckForProperty, skipTypeErrors: true); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Select(p => p.GetKeyText()) .Except(targetType.Properties.Values.Select(p => p.Name), LanguageConstants.IdentifierComparer) .Select(name => propertyMap[name]); if (targetType.AdditionalPropertiesType == null) { var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly)) .Select(p => p.Name) .Except(expression.Properties.Select(p => p.GetKeyText()), LanguageConstants.IdentifierComparer) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { var error = validUnspecifiedProperties.Any() ? DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedPropertyWithPermissibleProperties(extraProperty.GetKeyText(), targetType.Name, validUnspecifiedProperties) : DiagnosticBuilder.ForPosition(extraProperty.Key).DisallowedProperty(extraProperty.GetKeyText(), targetType.Name); diagnostics.AddRange(error.AsEnumerable()); } } else { // extra properties must be assignable to the right type foreach (ObjectPropertySyntax extraProperty in extraProperties) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheckForProperty == false && targetType.AdditionalPropertiesFlags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants diagnostics.AddRange(GetCompileTimeConstantViolation(extraProperty.Value)); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } GetExpressionAssignmentDiagnosticsInternal( typeManager, extraProperty.Value, targetType.AdditionalPropertiesType.Type, diagnostics, (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).PropertyTypeMismatch(extraProperty.GetKeyText(), expectedType, actualType), skipConstantCheckForProperty, skipTypeErrors: true); } } }
private static TypeSymbol NarrowDiscriminatedObjectType(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, IDiagnosticWriter diagnosticWriter, bool skipConstantCheck) { // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(LanguageConstants.Any); } var discriminatorProperty = expression.Properties.FirstOrDefault(x => LanguageConstants.IdentifierComparer.Equals(x.TryGetKeyText(), targetType.DiscriminatorKey)); if (discriminatorProperty == null) { // object doesn't contain the discriminator field diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); var propertyKeys = expression.Properties .Select(x => x.TryGetKeyText()) .Where(key => !string.IsNullOrEmpty(key)) .Select(key => key !); // do a reverse lookup to check if there's any misspelled discriminator key var misspelledDiscriminatorKey = SpellChecker.GetSpellingSuggestion(targetType.DiscriminatorKey, propertyKeys); if (misspelledDiscriminatorKey != null) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).DisallowedPropertyWithSuggestion(ShouldWarn(targetType), misspelledDiscriminatorKey, targetType.DiscriminatorKeysUnionType, targetType.DiscriminatorKey)); } return(LanguageConstants.Any); } // At some point in the future we may want to relax the expectation of a string literal key, and allow a generic string. // In this case, the best we can do is validate against the union of all the settable properties. // Let's not do this just yet, and see if a use-case arises. var discriminatorType = typeManager.GetTypeInfo(discriminatorProperty.Value); if (!(discriminatorType is StringLiteralType stringLiteralDiscriminator)) { diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(ShouldWarn(targetType), targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectReference)) { // no matches var discriminatorCandidates = targetType.UnionMembersByKey.Keys.OrderBy(x => x); string?suggestedDiscriminator = SpellChecker.GetSpellingSuggestion(stringLiteralDiscriminator.Name, discriminatorCandidates); var builder = DiagnosticBuilder.ForPosition(discriminatorProperty.Value); bool shouldWarn = ShouldWarn(targetType); diagnosticWriter.Write(suggestedDiscriminator != null ? builder.PropertyStringLiteralMismatchWithSuggestion(shouldWarn, targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, stringLiteralDiscriminator.Name, suggestedDiscriminator) : builder.PropertyTypeMismatch(shouldWarn, targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return(LanguageConstants.Any); } if (!(selectedObjectReference.Type is ObjectType selectedObjectType)) { throw new InvalidOperationException($"Discriminated type {targetType.Name} contains non-object member"); } // we have a match! return(NarrowObjectType(typeManager, expression, selectedObjectType, diagnosticWriter, skipConstantCheck)); }
public static IEnumerable <Diagnostic> GetExpressionAssignmentDiagnostics(ITypeManager typeManager, SyntaxBase expression, TypeSymbol targetType, Func <TypeSymbol, TypeSymbol, SyntaxBase, Diagnostic>?typeMismatchErrorFactory = null) { // generic error creator if a better one was not specified. typeMismatchErrorFactory ??= (expectedType, actualType, errorExpression) => DiagnosticBuilder.ForPosition(errorExpression).ExpectedValueTypeMismatch(expectedType.Name, actualType.Name); var diagnostics = new List <Diagnostic>(); GetExpressionAssignmentDiagnosticsInternal(typeManager, expression, targetType, diagnostics, typeMismatchErrorFactory, skipConstantCheck: false, skipTypeErrors: false); return(diagnostics); }
private Symbol LookupSymbolByName(string name, TextSpan span, NamespaceSymbol? @namespace) { NamespaceSymbol?FindNamespace(string name) { this.namespaces.TryGetValue(name, out NamespaceSymbol @namespace); return(@namespace); } Symbol?foundSymbol; if (@namespace == null) { // attempt to find name in the imported namespaces var namespaceSymbol = FindNamespace(name); if (namespaceSymbol != null) { // namespace symbol found return(ValidateFunctionFlags(namespaceSymbol, span)); } // 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(name, out var localSymbol)) { // we found the symbol in the local namespace return(ValidateFunctionFlags(localSymbol, span)); } // attempt to find function in all imported namespaces var foundSymbols = this.namespaces .Select(kvp => kvp.Value.TryGetFunctionSymbol(name)) .Where(symbol => symbol != null) .ToList(); if (foundSymbols.Count() > 1) { // ambiguous symbol return(new UnassignableSymbol(DiagnosticBuilder.ForPosition(span) .AmbiguousSymbolReference(name, this.namespaces.Keys))); } foundSymbol = foundSymbols.FirstOrDefault(); if (foundSymbol == null) { var nameCandidates = this.declarations.Values .Concat(this.namespaces.SelectMany(kvp => kvp.Value.Descendants)) .Select(symbol => symbol.Name) .ToImmutableSortedSet(); var suggestedName = SpellChecker.GetSpellingSuggestion(name, nameCandidates); return(suggestedName != null ? new UnassignableSymbol(DiagnosticBuilder.ForPosition(span).SymbolicNameDoesNotExistWithSuggestion(name, suggestedName)) : new UnassignableSymbol(DiagnosticBuilder.ForPosition(span).SymbolicNameDoesNotExist(name))); } } else { foundSymbol = @namespace.TryGetFunctionSymbol(name); if (foundSymbol == null) { return(new UnassignableSymbol(DiagnosticBuilder.ForPosition(span).FunctionNotFound(name, @namespace.Name))); } } return(ValidateFunctionFlags(foundSymbol, span)); }
private static TypeSymbol NarrowObjectType(ITypeManager typeManager, ObjectSyntax expression, ObjectType targetType, IDiagnosticWriter diagnosticWriter, bool skipConstantCheck) { // TODO: Short-circuit on any object to avoid unnecessary processing? // TODO: Consider doing the schema check even if there are parse errors // if we have parse errors, there's no point to check assignability // we should not return the parse errors however because they will get double collected if (expression.HasParseErrors()) { return(targetType); } var namedPropertyMap = expression.ToNamedPropertyDictionary(); var missingRequiredProperties = targetType.Properties.Values .Where(p => p.Flags.HasFlag(TypePropertyFlags.Required) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(p => p); if (missingRequiredProperties.Any()) { IPositionable positionable = expression; string blockName = "object"; var parent = typeManager.GetParent(expression); if (parent is ObjectPropertySyntax objectPropertyParent) { positionable = objectPropertyParent.Key; blockName = "object"; } else if (parent is INamedDeclarationSyntax declarationParent) { positionable = declarationParent.Name; blockName = declarationParent.Keyword.Text; } diagnosticWriter.Write(DiagnosticBuilder.ForPosition(positionable).MissingRequiredProperties(ShouldWarn(targetType), missingRequiredProperties, blockName)); } var narrowedProperties = new List <TypeProperty>(); foreach (var declaredProperty in targetType.Properties.Values) { if (namedPropertyMap.TryGetValue(declaredProperty.Name, out var declaredPropertySyntax)) { bool skipConstantCheckForProperty = skipConstantCheck; // is the property marked as requiring compile-time constants and has the parent already validated this? if (skipConstantCheck == false && declaredProperty.Flags.HasFlag(TypePropertyFlags.Constant)) { // validate that values are compile-time constants GetCompileTimeConstantViolation(declaredPropertySyntax.Value, diagnosticWriter); // disable compile-time constant validation for children skipConstantCheckForProperty = true; } if (declaredProperty.Flags.HasFlag(TypePropertyFlags.ReadOnly)) { // the declared property is read-only // value cannot be assigned to a read-only property diagnosticWriter.Write(DiagnosticBuilder.ForPosition(declaredPropertySyntax.Key).CannotAssignToReadOnlyProperty(ShouldWarn(targetType), declaredProperty.Name)); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, declaredProperty.TypeReference.Type, declaredProperty.Flags)); continue; } // declared property is specified in the value object // validate type var narrowedType = NarrowTypeInternal( typeManager, declaredPropertySyntax.Value, declaredProperty.TypeReference.Type, diagnosticWriter, GetPropertyMismatchErrorFactory(ShouldWarn(targetType), declaredProperty.Name), skipConstantCheckForProperty, skipTypeErrors: true); narrowedProperties.Add(new TypeProperty(declaredProperty.Name, narrowedType, declaredProperty.Flags)); } else { narrowedProperties.Add(declaredProperty); } } // find properties that are specified on in the expression object but not declared in the schema var extraProperties = expression.Properties .Where(p => !(p.TryGetKeyText() is string keyName) || !targetType.Properties.ContainsKey(keyName)); if (targetType.AdditionalPropertiesType == null) { bool shouldWarn = ShouldWarn(targetType); var validUnspecifiedProperties = targetType.Properties.Values .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly) && !namedPropertyMap.ContainsKey(p.Name)) .Select(p => p.Name) .OrderBy(x => x); // extra properties are not allowed by the type foreach (var extraProperty in extraProperties) { Diagnostic error; var builder = DiagnosticBuilder.ForPosition(extraProperty.Key); if (extraProperty.TryGetKeyText() is string keyName) { error = validUnspecifiedProperties.Any() switch { true => SpellChecker.GetSpellingSuggestion(keyName, validUnspecifiedProperties) switch { string suggestedKeyName when suggestedKeyName != null => builder.DisallowedPropertyWithSuggestion(shouldWarn, keyName, targetType, suggestedKeyName), _ => builder.DisallowedPropertyWithPermissibleProperties(shouldWarn, keyName, targetType, validUnspecifiedProperties) },