// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Represents compilation options common to C# and VB.
    /// </summary>
    public abstract class CompilationOptions
    {
        /// <summary>
        /// The kind of assembly generated when emitted.
        /// </summary>
        public OutputKind OutputKind { get; protected set; }

        /// <summary>
        /// Name of the primary module, or null if a default name should be used.
        /// </summary>
        /// <remarks>
        /// The name usually (but not necessarily) includes an extension, e.g. "MyModule.dll".
        /// 
        /// If <see cref="ModuleName"/> is null the actual name written to metadata  
        /// is derived from the name of the compilation (<see cref="Compilation.AssemblyName"/>)
        /// by appending a default extension for <see cref="OutputKind"/>.
        /// </remarks>
        public string? ModuleName { get; protected set; }

        /// <summary>
        /// The full name of a global implicit class (script class). This class implicitly encapsulates top-level statements, 
        /// type declarations, and member declarations. Could be a namespace qualified name.
        /// </summary>
        public string? ScriptClassName { get; protected set; }

        /// <summary>
        /// The full name of a type that declares static Main method. Must be a valid non-generic namespace-qualified name.
        /// Null if any static Main method is a candidate for an entry point.
        /// </summary>
        public string? MainTypeName { get; protected set; }

        // Note that we avoid using default(ImmutableArray<byte>) for unspecified value since 
        // such value is currently not serializable by JSON serializer.

        /// <summary>
        /// Specifies public key used to generate strong name for the compilation assembly, or empty if not specified.
        /// </summary>
        /// <remarks>
        /// If specified the values of <see cref="CryptoKeyFile"/> and <see cref="CryptoKeyContainer"/>
        /// must be null. If <see cref="PublicSign"/> is true the assembly is marked as fully signed
        /// but only signed with the public key (aka "OSS signing").
        /// </remarks>
        public ImmutableArray<byte> CryptoPublicKey { get; protected set; }

        /// <summary>
        /// The name of the file containing the public and private keys to use to generate strong name of the 
        /// compilation assembly and to sign it.
        /// </summary>
        /// <remarks>
        /// <para>
        /// To sign the output supply either one of <see cref="CryptoKeyFile"/> or <see cref="CryptoKeyContainer"/>.
        /// but not both. If both are specified <see cref="CryptoKeyContainer"/> is ignored.
        /// </para>
        /// <para>
        /// If <see cref="PublicSign" /> is also set, <see cref="CryptoKeyFile"/> must be the absolute
        /// path to key file.
        /// </para>
        /// </remarks>
        public string? CryptoKeyFile { get; protected set; }

        /// <summary>
        /// The CSP container containing the key with which to sign the output.
        /// </summary>
        /// <remarks>
        /// <para>
        /// To sign the output supply either one of <see cref="CryptoKeyFile"/> or <see cref="CryptoKeyContainer"/>.
        /// but not both. If both are specified <see cref="CryptoKeyContainer"/> is ignored.
        /// </para>
        /// <para>
        /// This setting is obsolete and only supported on Microsoft Windows platform.
        /// Use <see cref="CryptoPublicKey"/> to generate assemblies with strong name and 
        /// a signing tool (Microsoft .NET Framework Strong Name Utility (sn.exe) or equivalent) to sign them.
        /// </para>
        /// </remarks>
        public string? CryptoKeyContainer { get; protected set; }

        /// <summary>
        /// Mark the compilation assembly as delay-signed.
        /// </summary>
        /// <remarks>
        /// If true the resulting assembly is marked as delay signed.
        /// 
        /// If false and <see cref="CryptoPublicKey"/>, <see cref="CryptoKeyFile"/>, or <see cref="CryptoKeyContainer"/> is specified
        /// or attribute System.Reflection.AssemblyKeyFileAttribute or System.Reflection.AssemblyKeyNameAttribute is applied to the 
        /// compilation assembly in source the resulting assembly is signed accordingly to the specified values/attributes.
        /// 
        /// If null the semantics is specified by the value of attribute System.Reflection.AssemblyDelaySignAttribute 
        /// applied to the compilation assembly in source. If the attribute is not present the value defaults to "false".
        /// </remarks>
        public bool? DelaySign { get; protected set; }

        /// <summary>
        /// Mark the compilation assembly as fully signed, but only sign with the public key.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If true, the assembly is marked as signed, but is only signed with the public key.
        /// </para>
        /// <para>
        /// The key must be provided through either an absolute path in <see cref="CryptoKeyFile"/>
        /// or directly via <see cref="CryptoPublicKey" />.
        /// </para>
        /// </remarks>
        public bool PublicSign { get; protected set; }

        /// <summary>
        /// Whether bounds checking on integer arithmetic is enforced by default or not.
        /// </summary>
        public bool CheckOverflow { get; protected set; }

        /// <summary>
        /// Specifies which version of the common language runtime (CLR) can run the assembly.
        /// </summary>
        public Platform Platform { get; protected set; }

        /// <summary>
        /// Specifies whether or not optimizations should be performed on the output IL.
        /// This is independent of whether or not PDB information is generated.
        /// </summary>
        public OptimizationLevel OptimizationLevel { get; protected set; }

        /// <summary>
        /// Global warning report option
        /// </summary>
        public ReportDiagnostic GeneralDiagnosticOption { get; protected set; }

        /// <summary>
        /// Global warning level (a non-negative integer).
        /// </summary>
        public int WarningLevel { get; protected set; }

        /// <summary>
        /// Specifies whether building compilation may use multiple threads.
        /// </summary>
        public bool ConcurrentBuild { get; protected set; }

        /// <summary>
        /// Specifies whether the compilation should be deterministic.
        /// </summary>
        public bool Deterministic { get; protected set; }

        /// <summary>
        /// Used for time-based version generation when <see cref="System.Reflection.AssemblyVersionAttribute"/> contains a wildcard.
        /// If equal to default(<see cref="DateTime"/>) the actual current local time will be used.
        /// </summary>
        internal DateTime CurrentLocalTime { get; private protected set; }

        /// <summary>
        /// Emit mode that favors debuggability. 
        /// </summary>
        internal bool DebugPlusMode { get; private protected set; }

        /// <summary>
        /// Specifies whether to import members with accessibility other than public or protected by default. 
        /// Default value is <see cref="MetadataImportOptions.Public"/>. The value specified is not going to 
        /// affect correctness of analysis performed by compilers because all members needed for correctness 
        /// are going to be imported regardless. This setting can force compilation to import members that it 
        /// normally doesn't.
        /// </summary>
        public MetadataImportOptions MetadataImportOptions { get; protected set; }

        /// <summary>
        /// Apply additional disambiguation rules during resolution of referenced assemblies.
        /// </summary>
        internal bool ReferencesSupersedeLowerVersions { get; private protected set; }

        /// <summary>
        /// Modifies the incoming diagnostic, for example escalating its severity, or discarding it (returning null) based on the compilation options.
        /// </summary>
        /// <param name="diagnostic"></param>
        /// <returns>The modified diagnostic, or null</returns>
        internal abstract Diagnostic? FilterDiagnostic(Diagnostic diagnostic, CancellationToken cancellationToken);

        /// <summary>
        /// Warning report option for each warning.
        /// </summary>
        public ImmutableDictionary<string, ReportDiagnostic> SpecificDiagnosticOptions { get; protected set; }

        /// <summary>
        /// Provider to retrieve options for particular syntax trees.
        /// </summary>
        public SyntaxTreeOptionsProvider? SyntaxTreeOptionsProvider { get; protected set; }

        /// <summary>
        /// Whether diagnostics suppressed in source, i.e. <see cref="Diagnostic.IsSuppressed"/> is true, should be reported.
        /// </summary>
        public bool ReportSuppressedDiagnostics { get; protected set; }

        /// <summary>
        /// Resolves paths to metadata references specified in source via #r directives.
        /// Null if the compilation can't contain references to metadata other than those explicitly passed to its factory (such as #r directives in sources). 
        /// </summary>
        public MetadataReferenceResolver? MetadataReferenceResolver { get; protected set; }

        /// <summary>
        /// Gets the resolver for resolving XML document references for the compilation.
        /// Null if the compilation is not allowed to contain XML file references, such as XML doc comment include tags and permission sets stored in an XML file.
        /// </summary>
        public XmlReferenceResolver? XmlReferenceResolver { get; protected set; }

        /// <summary>
        /// Gets the resolver for resolving source document references for the compilation.
        /// Null if the compilation is not allowed to contain source file references, such as #line pragmas and #load directives.
        /// </summary>
        public SourceReferenceResolver? SourceReferenceResolver { get; protected set; }

        /// <summary>
        /// Provides strong name and signature the source assembly.
        /// Null if assembly signing is not supported.
        /// </summary>
        public StrongNameProvider? StrongNameProvider { get; protected set; }

        /// <summary>
        /// Used to compare assembly identities. May implement unification and portability policies specific to the target platform.
        /// <see cref="AssemblyIdentityComparer.Default"/> if not specified.
        /// </summary>
        public AssemblyIdentityComparer AssemblyIdentityComparer { get; protected set; }

        /// <summary>
        /// Gets the default nullable context state in this compilation.
        /// </summary>
        /// <remarks>
        /// This context does not apply to files that are marked as generated. Nullable is off
        /// by default in those locations.
        /// </remarks>
        public abstract NullableContextOptions NullableContextOptions { get; protected set; }

        /// <summary>
        /// A set of strings designating experimental compiler features that are to be enabled.
        /// </summary>
        [Obsolete]
        protected internal ImmutableArray<string> Features
        {
            get
            {
                throw new NotImplementedException();
            }
            protected set
            {
                throw new NotImplementedException();
            }
        }

        private readonly Lazy<ImmutableArray<Diagnostic>> _lazyErrors;

        // Expects correct arguments.
        internal CompilationOptions(
            OutputKind outputKind,
            bool reportSuppressedDiagnostics,
            string? moduleName,
            string? mainTypeName,
            string? scriptClassName,
            string? cryptoKeyContainer,
            string? cryptoKeyFile,
            ImmutableArray<byte> cryptoPublicKey,
            bool? delaySign,
            bool publicSign,
            OptimizationLevel optimizationLevel,
            bool checkOverflow,
            Platform platform,
            ReportDiagnostic generalDiagnosticOption,
            int warningLevel,
            ImmutableDictionary<string, ReportDiagnostic> specificDiagnosticOptions,
            bool concurrentBuild,
            bool deterministic,
            DateTime currentLocalTime,
            bool debugPlusMode,
            XmlReferenceResolver? xmlReferenceResolver,
            SourceReferenceResolver? sourceReferenceResolver,
            SyntaxTreeOptionsProvider? syntaxTreeOptionsProvider,
            MetadataReferenceResolver? metadataReferenceResolver,
            AssemblyIdentityComparer? assemblyIdentityComparer,
            StrongNameProvider? strongNameProvider,
            MetadataImportOptions metadataImportOptions,
            bool referencesSupersedeLowerVersions)
        {
            this.OutputKind = outputKind;
            this.ModuleName = moduleName;
            this.MainTypeName = mainTypeName;
            this.ScriptClassName = scriptClassName ?? WellKnownMemberNames.DefaultScriptClassName;
            this.CryptoKeyContainer = cryptoKeyContainer;
            this.CryptoKeyFile = string.IsNullOrEmpty(cryptoKeyFile) ? null : cryptoKeyFile;
            this.CryptoPublicKey = cryptoPublicKey.NullToEmpty();
            this.DelaySign = delaySign;
            this.CheckOverflow = checkOverflow;
            this.Platform = platform;
            this.GeneralDiagnosticOption = generalDiagnosticOption;
            this.WarningLevel = warningLevel;
            this.SpecificDiagnosticOptions = specificDiagnosticOptions;
            this.ReportSuppressedDiagnostics = reportSuppressedDiagnostics;
            this.OptimizationLevel = optimizationLevel;
            this.ConcurrentBuild = concurrentBuild;
            this.Deterministic = deterministic;
            this.CurrentLocalTime = currentLocalTime;
            this.DebugPlusMode = debugPlusMode;
            this.XmlReferenceResolver = xmlReferenceResolver;
            this.SourceReferenceResolver = sourceReferenceResolver;
            this.SyntaxTreeOptionsProvider = syntaxTreeOptionsProvider;
            this.MetadataReferenceResolver = metadataReferenceResolver;
            this.StrongNameProvider = strongNameProvider;
            this.AssemblyIdentityComparer = assemblyIdentityComparer ?? AssemblyIdentityComparer.Default;
            this.MetadataImportOptions = metadataImportOptions;
            this.ReferencesSupersedeLowerVersions = referencesSupersedeLowerVersions;
            this.PublicSign = publicSign;

            _lazyErrors = new Lazy<ImmutableArray<Diagnostic>>(() =>
            {
                var builder = ArrayBuilder<Diagnostic>.GetInstance();
                ValidateOptions(builder);
                return builder.ToImmutableAndFree();
            });
        }

        internal bool CanReuseCompilationReferenceManager(CompilationOptions other)
        {
            // This condition has to include all options the Assembly Manager depends on when binding references.
            // In addition, the assembly name is determined based upon output kind. It is special for netmodules.
            // Can't reuse when file resolver or identity comparers change.
            // Can reuse even if StrongNameProvider changes. When resolving a cyclic reference only the simple name is considered, not the strong name.
            return this.MetadataImportOptions == other.MetadataImportOptions
                && this.ReferencesSupersedeLowerVersions == other.ReferencesSupersedeLowerVersions
                && this.OutputKind.IsNetModule() == other.OutputKind.IsNetModule()
                && object.Equals(this.XmlReferenceResolver, other.XmlReferenceResolver)
                && object.Equals(this.MetadataReferenceResolver, other.MetadataReferenceResolver)
                && object.Equals(this.AssemblyIdentityComparer, other.AssemblyIdentityComparer);
        }

        /// <summary>
        /// Gets the source language ("C#" or "Visual Basic").
        /// </summary>
        public abstract string Language { get; }

        internal bool EnableEditAndContinue
        {
            get
            {
                return OptimizationLevel == OptimizationLevel.Debug;
            }
        }

        internal static bool IsValidFileAlignment(int value)
        {
            switch (value)
            {
                case 512:
                case 1024:
                case 2048:
                case 4096:
                case 8192:
                    return true;

                default:
                    return false;
            }
        }

        internal abstract ImmutableArray<string> GetImports();

        /// <summary>
        /// Creates a new options instance with the specified general diagnostic option.
        /// </summary>
        public CompilationOptions WithGeneralDiagnosticOption(ReportDiagnostic value)
        {
            return CommonWithGeneralDiagnosticOption(value);
        }

        /// <summary>
        /// Creates a new options instance with the specified diagnostic-specific options.
        /// </summary>
        public CompilationOptions WithSpecificDiagnosticOptions(ImmutableDictionary<string, ReportDiagnostic>? value)
        {
            return CommonWithSpecificDiagnosticOptions(value);
        }

        /// <summary>
        /// Creates a new options instance with the specified diagnostic-specific options.
        /// </summary>
        public CompilationOptions WithSpecificDiagnosticOptions(IEnumerable<KeyValuePair<string, ReportDiagnostic>> value)
        {
            return CommonWithSpecificDiagnosticOptions(value);
        }

        /// <summary>
        /// Creates a new options instance with the specified suppressed diagnostics reporting option.
        /// </summary>
        public CompilationOptions WithReportSuppressedDiagnostics(bool value)
        {
            return CommonWithReportSuppressedDiagnostics(value);
        }

        /// <summary>
        /// Creates a new options instance with the concurrent build property set accordingly.
        /// </summary>
        public CompilationOptions WithConcurrentBuild(bool concurrent)
        {
            return CommonWithConcurrentBuild(concurrent);
        }

        /// <summary>
        /// Creates a new options instance with the deterministic property set accordingly.
        /// </summary>
        public CompilationOptions WithDeterministic(bool deterministic)
        {
            return CommonWithDeterministic(deterministic);
        }

        /// <summary>
        /// Creates a new options instance with the specified output kind.
        /// </summary>
        public CompilationOptions WithOutputKind(OutputKind kind)
        {
            return CommonWithOutputKind(kind);
        }

        /// <summary>
        /// Creates a new options instance with the specified platform.
        /// </summary>
        public CompilationOptions WithPlatform(Platform platform)
        {
            return CommonWithPlatform(platform);
        }

        /// <summary>
        /// Creates a new options instance with the specified public sign setting.
        /// </summary>
        public CompilationOptions WithPublicSign(bool publicSign) => CommonWithPublicSign(publicSign);

        /// <summary>
        /// Creates a new options instance with optimizations enabled or disabled.
        /// </summary>
        public CompilationOptions WithOptimizationLevel(OptimizationLevel value)
        {
            return CommonWithOptimizationLevel(value);
        }

        public CompilationOptions WithXmlReferenceResolver(XmlReferenceResolver? resolver)
        {
            return CommonWithXmlReferenceResolver(resolver);
        }

        public CompilationOptions WithSourceReferenceResolver(SourceReferenceResolver? resolver)
        {
            return CommonWithSourceReferenceResolver(resolver);
        }

        public CompilationOptions WithSyntaxTreeOptionsProvider(SyntaxTreeOptionsProvider? provider)
        {
            return CommonWithSyntaxTreeOptionsProvider(provider);
        }

        public CompilationOptions WithMetadataReferenceResolver(MetadataReferenceResolver? resolver)
        {
            return CommonWithMetadataReferenceResolver(resolver);
        }

        public CompilationOptions WithAssemblyIdentityComparer(AssemblyIdentityComparer comparer)
        {
            return CommonWithAssemblyIdentityComparer(comparer);
        }

        public CompilationOptions WithStrongNameProvider(StrongNameProvider? provider)
        {
            return CommonWithStrongNameProvider(provider);
        }

        public CompilationOptions WithModuleName(string? moduleName)
        {
            return CommonWithModuleName(moduleName);
        }

        public CompilationOptions WithMainTypeName(string? mainTypeName)
        {
            return CommonWithMainTypeName(mainTypeName);
        }

        public CompilationOptions WithScriptClassName(string scriptClassName)
        {
            return CommonWithScriptClassName(scriptClassName);
        }

        public CompilationOptions WithCryptoKeyContainer(string? cryptoKeyContainer)
        {
            return CommonWithCryptoKeyContainer(cryptoKeyContainer);
        }

        public CompilationOptions WithCryptoKeyFile(string? cryptoKeyFile)
        {
            return CommonWithCryptoKeyFile(cryptoKeyFile);
        }

        public CompilationOptions WithCryptoPublicKey(ImmutableArray<byte> cryptoPublicKey)
        {
            return CommonWithCryptoPublicKey(cryptoPublicKey);
        }

        public CompilationOptions WithDelaySign(bool? delaySign)
        {
            return CommonWithDelaySign(delaySign);
        }

        public CompilationOptions WithOverflowChecks(bool checkOverflow)
        {
            return CommonWithCheckOverflow(checkOverflow);
        }

        public CompilationOptions WithMetadataImportOptions(MetadataImportOptions value) => CommonWithMetadataImportOptions(value);

        protected abstract CompilationOptions CommonWithConcurrentBuild(bool concurrent);
        protected abstract CompilationOptions CommonWithDeterministic(bool deterministic);
        protected abstract CompilationOptions CommonWithOutputKind(OutputKind kind);
        protected abstract CompilationOptions CommonWithPlatform(Platform platform);
        protected abstract CompilationOptions CommonWithPublicSign(bool publicSign);
        protected abstract CompilationOptions CommonWithOptimizationLevel(OptimizationLevel value);
        protected abstract CompilationOptions CommonWithXmlReferenceResolver(XmlReferenceResolver? resolver);
        protected abstract CompilationOptions CommonWithSourceReferenceResolver(SourceReferenceResolver? resolver);
        protected abstract CompilationOptions CommonWithSyntaxTreeOptionsProvider(SyntaxTreeOptionsProvider? resolver);
        protected abstract CompilationOptions CommonWithMetadataReferenceResolver(MetadataReferenceResolver? resolver);
        protected abstract CompilationOptions CommonWithAssemblyIdentityComparer(AssemblyIdentityComparer? comparer);
        protected abstract CompilationOptions CommonWithStrongNameProvider(StrongNameProvider? provider);
        protected abstract CompilationOptions CommonWithGeneralDiagnosticOption(ReportDiagnostic generalDiagnosticOption);
        protected abstract CompilationOptions CommonWithSpecificDiagnosticOptions(ImmutableDictionary<string, ReportDiagnostic>? specificDiagnosticOptions);
        protected abstract CompilationOptions CommonWithSpecificDiagnosticOptions(IEnumerable<KeyValuePair<string, ReportDiagnostic>> specificDiagnosticOptions);
        protected abstract CompilationOptions CommonWithReportSuppressedDiagnostics(bool reportSuppressedDiagnostics);
        protected abstract CompilationOptions CommonWithModuleName(string? moduleName);
        protected abstract CompilationOptions CommonWithMainTypeName(string? mainTypeName);
        protected abstract CompilationOptions CommonWithScriptClassName(string scriptClassName);
        protected abstract CompilationOptions CommonWithCryptoKeyContainer(string? cryptoKeyContainer);
        protected abstract CompilationOptions CommonWithCryptoKeyFile(string? cryptoKeyFile);
        protected abstract CompilationOptions CommonWithCryptoPublicKey(ImmutableArray<byte> cryptoPublicKey);
        protected abstract CompilationOptions CommonWithDelaySign(bool? delaySign);
        protected abstract CompilationOptions CommonWithCheckOverflow(bool checkOverflow);
        protected abstract CompilationOptions CommonWithMetadataImportOptions(MetadataImportOptions value);

        [Obsolete]
        protected abstract CompilationOptions CommonWithFeatures(ImmutableArray<string> features);

        /// <summary>
        /// Performs validation of options compatibilities and generates diagnostics if needed
        /// </summary>
        internal abstract void ValidateOptions(ArrayBuilder<Diagnostic> builder);

        internal void ValidateOptions(ArrayBuilder<Diagnostic> builder, CommonMessageProvider messageProvider)
        {
            if (!CryptoPublicKey.IsEmpty)
            {
                if (CryptoKeyFile != null)
                {
                    builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_MutuallyExclusiveOptions,
                        Location.None, nameof(CryptoPublicKey), nameof(CryptoKeyFile)));
                }

                if (CryptoKeyContainer != null)
                {
                    builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_MutuallyExclusiveOptions,
                        Location.None, nameof(CryptoPublicKey), nameof(CryptoKeyContainer)));
                }
            }

            if (PublicSign)
            {
                if (CryptoKeyFile != null && !PathUtilities.IsAbsolute(CryptoKeyFile))
                {
                    builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_OptionMustBeAbsolutePath,
                        Location.None, nameof(CryptoKeyFile)));
                }

                if (CryptoKeyContainer != null)
                {
                    builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_MutuallyExclusiveOptions,
                        Location.None, nameof(PublicSign), nameof(CryptoKeyContainer)));
                }

                if (DelaySign == true)
                {
                    builder.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_MutuallyExclusiveOptions,
                        Location.None, nameof(PublicSign), nameof(DelaySign)));
                }
            }
        }

        /// <summary>
        /// Errors collection related to an incompatible set of compilation options
        /// </summary>
        public ImmutableArray<Diagnostic> Errors
        {
            get { return _lazyErrors.Value; }
        }

        public abstract override bool Equals(object? obj);

        protected bool EqualsHelper([NotNullWhen(true)] CompilationOptions? other)
        {
            if (object.ReferenceEquals(other, null))
            {
                return false;
            }

            // NOTE: StringComparison.Ordinal is used for type name comparisons, even for VB.  That's because
            // a change in the canonical case should still change the option.
            bool equal =
                   this.CheckOverflow == other.CheckOverflow &&
                   this.ConcurrentBuild == other.ConcurrentBuild &&
                   this.Deterministic == other.Deterministic &&
                   this.CurrentLocalTime == other.CurrentLocalTime &&
                   this.DebugPlusMode == other.DebugPlusMode &&
                   string.Equals(this.CryptoKeyContainer, other.CryptoKeyContainer, StringComparison.Ordinal) &&
                   string.Equals(this.CryptoKeyFile, other.CryptoKeyFile, StringComparison.Ordinal) &&
                   this.CryptoPublicKey.SequenceEqual(other.CryptoPublicKey) &&
                   this.DelaySign == other.DelaySign &&
                   this.GeneralDiagnosticOption == other.GeneralDiagnosticOption &&
                   string.Equals(this.MainTypeName, other.MainTypeName, StringComparison.Ordinal) &&
                   this.MetadataImportOptions == other.MetadataImportOptions &&
                   this.ReferencesSupersedeLowerVersions == other.ReferencesSupersedeLowerVersions &&
                   string.Equals(this.ModuleName, other.ModuleName, StringComparison.Ordinal) &&
                   this.OptimizationLevel == other.OptimizationLevel &&
                   this.OutputKind == other.OutputKind &&
                   this.Platform == other.Platform &&
                   this.ReportSuppressedDiagnostics == other.ReportSuppressedDiagnostics &&
                   string.Equals(this.ScriptClassName, other.ScriptClassName, StringComparison.Ordinal) &&
                   this.SpecificDiagnosticOptions.SequenceEqual(other.SpecificDiagnosticOptions, (left, right) => (left.Key == right.Key) && (left.Value == right.Value)) &&
                   this.WarningLevel == other.WarningLevel &&
                   object.Equals(this.MetadataReferenceResolver, other.MetadataReferenceResolver) &&
                   object.Equals(this.XmlReferenceResolver, other.XmlReferenceResolver) &&
                   object.Equals(this.SourceReferenceResolver, other.SourceReferenceResolver) &&
                   object.Equals(this.SyntaxTreeOptionsProvider, other.SyntaxTreeOptionsProvider) &&
                   object.Equals(this.StrongNameProvider, other.StrongNameProvider) &&
                   object.Equals(this.AssemblyIdentityComparer, other.AssemblyIdentityComparer) &&
                   this.PublicSign == other.PublicSign &&
                   this.NullableContextOptions == other.NullableContextOptions;

            return equal;
        }

        public abstract override int GetHashCode();

        protected int GetHashCodeHelper()
        {
            return Hash.Combine(this.CheckOverflow,
                   Hash.Combine(this.ConcurrentBuild,
                   Hash.Combine(this.Deterministic,
                   Hash.Combine(this.CurrentLocalTime.GetHashCode(),
                   Hash.Combine(this.DebugPlusMode,
                   Hash.Combine(this.CryptoKeyContainer != null ? StringComparer.Ordinal.GetHashCode(this.CryptoKeyContainer) : 0,
                   Hash.Combine(this.CryptoKeyFile != null ? StringComparer.Ordinal.GetHashCode(this.CryptoKeyFile) : 0,
                   Hash.Combine(Hash.CombineValues(this.CryptoPublicKey, 16),
                   Hash.Combine((int)this.GeneralDiagnosticOption,
                   Hash.Combine(this.MainTypeName != null ? StringComparer.Ordinal.GetHashCode(this.MainTypeName) : 0,
                   Hash.Combine((int)this.MetadataImportOptions,
                   Hash.Combine(this.ReferencesSupersedeLowerVersions,
                   Hash.Combine(this.ModuleName != null ? StringComparer.Ordinal.GetHashCode(this.ModuleName) : 0,
                   Hash.Combine((int)this.OptimizationLevel,
                   Hash.Combine((int)this.OutputKind,
                   Hash.Combine((int)this.Platform,
                   Hash.Combine(this.ReportSuppressedDiagnostics,
                   Hash.Combine(this.ScriptClassName != null ? StringComparer.Ordinal.GetHashCode(this.ScriptClassName) : 0,
                   Hash.Combine(Hash.CombineValues(this.SpecificDiagnosticOptions),
                   Hash.Combine(this.WarningLevel,
                   Hash.Combine(this.MetadataReferenceResolver,
                   Hash.Combine(this.XmlReferenceResolver,
                   Hash.Combine(this.SourceReferenceResolver,
                   Hash.Combine(this.SyntaxTreeOptionsProvider,
                   Hash.Combine(this.StrongNameProvider,
                   Hash.Combine(this.AssemblyIdentityComparer,
                   Hash.Combine(this.PublicSign,
                   Hash.Combine((int)this.NullableContextOptions, 0))))))))))))))))))))))))))));
        }

        public static bool operator ==(CompilationOptions? left, CompilationOptions? right)
        {
            return object.Equals(left, right);
        }

        public static bool operator !=(CompilationOptions? left, CompilationOptions? right)
        {
            return !object.Equals(left, right);
        }
    }
}
