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)); }
private static void GetDiscriminatedObjectAssignmentDiagnostics(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, IList <Diagnostic> diagnostics, 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; } var propertyMap = expression.ToPropertyDictionary(); if (!propertyMap.TryGetValue(targetType.DiscriminatorKey, out var discriminatorProperty)) { // object doesn't contain the discriminator field diagnostics.Add(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); return; } // 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)) { diagnostics.Add(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return; } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectReference)) { // no matches diagnostics.Add(DiagnosticBuilder.ForPosition(discriminatorProperty.Value).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); return; } if (!(selectedObjectReference.Type is ObjectType selectedObjectType)) { throw new InvalidOperationException($"Discriminated type {targetType.Name} contains non-object member"); } // we have a match! GetObjectAssignmentDiagnostics(typeManager, expression, selectedObjectType, diagnostics, skipConstantCheck); }
private static IEnumerable <ErrorDiagnostic> GetDiscriminatedObjectAssignmentDiagnostics(ITypeManager typeManager, ObjectSyntax expression, DiscriminatedObjectType targetType, 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()) { yield break; } var propertyMap = expression.ToPropertyDictionary(); if (!propertyMap.TryGetValue(targetType.DiscriminatorKey, out var discriminatorProperty)) { // object doesn't contain the discriminator field yield return(DiagnosticBuilder.ForPosition(expression).MissingRequiredProperty(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType)); yield break; } // 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, new TypeManagerContext()); if (!(discriminatorType is StringLiteralType stringLiteralDiscriminator)) { yield return(DiagnosticBuilder.ForPosition(expression).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); yield break; } if (!targetType.UnionMembersByKey.TryGetValue(stringLiteralDiscriminator.Name, out var selectedObjectType)) { // no matches yield return(DiagnosticBuilder.ForPosition(discriminatorProperty.Value).PropertyTypeMismatch(targetType.DiscriminatorKey, targetType.DiscriminatorKeysUnionType, discriminatorType)); yield break; } // we have a match! foreach (var diagnostic in GetObjectAssignmentDiagnostics(typeManager, expression, selectedObjectType, skipConstantCheck)) { yield return(diagnostic); } yield break; }