<commit_before>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.FindSymbols
{
    internal partial class SyntaxTreeInfo
    {
        /// <summary>
        /// snapshot based cache to guarantee same info is returned without re-calculating for same solution snapshot.
        /// since document will be re-created per new solution, this should go away as soon as there is any change on workspace.
        /// </summary>
        private static readonly ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo> s_identifierSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo>();
        private static readonly ConditionalWeakTable<Document, SyntaxTreeContextInfo> s_contextSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeContextInfo>();
        private static readonly ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo> s_declaredSymbolsSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo>();

        public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken)
        {
            Contract.Requires(document.IsFromPrimaryBranch());

            await PrecalculateBasicInfoAsync(document, cancellationToken).ConfigureAwait(false);

            // for now, don't put identifier locations in esent db
            //// await PrecalculateAdvancedInfoAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task PrecalculateAdvancedInfoAsync(Document document, CancellationToken cancellationToken)
        {
            // we do not support precalculating opened file.
            if (document.IsOpen())
            {
                return;
            }

            // we already have information. move on
            if (await SyntaxTreeIdentifierInfo.IdentifierSetPrecalculatedAsync(document, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            await SyntaxTreeIdentifierInfo.SaveIdentifierSetAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task PrecalculateBasicInfoAsync(Document document, CancellationToken cancellationToken)
        {
            // we already have information. move on
            if (await SyntaxTreeIdentifierInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) &&
                await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) &&
                await SyntaxTreeDeclarationInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false);

            await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task<T> GetInfoAsync<T>(
            Document document,
            ConditionalWeakTable<Document, T> cache,
            Func<Document, CancellationToken, Task<T>> generator,
            Func<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>, T> selector,
            CancellationToken cancellationToken)
            where T : class
        {
            T info;
            if (cache.TryGetValue(document, out info))
            {
                return info;
            }

            info = await generator(document, cancellationToken).ConfigureAwait(false);
            if (info != null)
            {
                return cache.GetValue(document, _ => info);
            }

            // alright, we don't have cached information, re-calculate them here.
            var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false);

            // okay, persist this info
            await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false);

            info = selector(data);
            return cache.GetValue(document, _ => info);
        }

        public static Task<SyntaxTreeContextInfo> GetContextInfoAsync(Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(document, s_contextSnapshotCache, SyntaxTreeContextInfo.LoadAsync, tuple => tuple.Item2, cancellationToken);
        }

        public static Task<SyntaxTreeIdentifierInfo> GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(document, s_identifierSnapshotCache, SyntaxTreeIdentifierInfo.LoadAsync, tuple => tuple.Item1, cancellationToken);
        }

        public static Task<SyntaxTreeDeclarationInfo> GetDeclarationInfoAsync(Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(document, s_declaredSymbolsSnapshotCache, SyntaxTreeDeclarationInfo.LoadAsync, tuple => tuple.Item3, cancellationToken);
        }

        // The probability of getting a false positive when calling ContainsIdentifier.
        private const double FalsePositiveProbability = 0.0001;

        private static async Task<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>> CreateInfoAsync(Document document, CancellationToken cancellationToken)
        {
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
            var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive;
            var isCaseSensitive = !ignoreCase;

            HashSet<string> identifiers;
            HashSet<string> escapedIdentifiers;
            GetIdentifierSet(ignoreCase, out identifiers, out escapedIdentifiers);

            try
            {
                var containsForEachStatement = false;
                var containsLockStatement = false;
                var containsUsingStatement = false;
                var containsQueryExpression = false;
                var containsThisConstructorInitializer = false;
                var containsBaseConstructorInitializer = false;
                var containsElementAccess = false;
                var containsIndexerMemberCref = false;

                var predefinedTypes = (int)PredefinedType.None;
                var predefinedOperators = (int)PredefinedOperator.None;

                var declaredSymbolInfos = new List<DeclaredSymbolInfo>();
<commit_msg>Cache delegates to prevent allocations.<commit_after>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.FindSymbols
{
    internal partial class SyntaxTreeInfo
    {
        /// <summary>
        /// snapshot based cache to guarantee same info is returned without re-calculating for same solution snapshot.
        /// since document will be re-created per new solution, this should go away as soon as there is any change on workspace.
        /// </summary>
        private static readonly ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo> s_identifierSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeIdentifierInfo>();
        private static readonly ConditionalWeakTable<Document, SyntaxTreeContextInfo> s_contextSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeContextInfo>();
        private static readonly ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo> s_declaredSymbolsSnapshotCache = new ConditionalWeakTable<Document, SyntaxTreeDeclarationInfo>();

        public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken)
        {
            Contract.Requires(document.IsFromPrimaryBranch());

            await PrecalculateBasicInfoAsync(document, cancellationToken).ConfigureAwait(false);

            // for now, don't put identifier locations in esent db
            //// await PrecalculateAdvancedInfoAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task PrecalculateAdvancedInfoAsync(Document document, CancellationToken cancellationToken)
        {
            // we do not support precalculating opened file.
            if (document.IsOpen())
            {
                return;
            }

            // we already have information. move on
            if (await SyntaxTreeIdentifierInfo.IdentifierSetPrecalculatedAsync(document, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            await SyntaxTreeIdentifierInfo.SaveIdentifierSetAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task PrecalculateBasicInfoAsync(Document document, CancellationToken cancellationToken)
        {
            // we already have information. move on
            if (await SyntaxTreeIdentifierInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) &&
                await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) &&
                await SyntaxTreeDeclarationInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false);

            await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false);
        }

        private static async Task<T> GetInfoAsync<T>(
            Document document,
            ConditionalWeakTable<Document, T> cache,
            Func<Document, CancellationToken, Task<T>> generator,
            Func<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>, T> selector,
            CancellationToken cancellationToken)
            where T : class
        {
            T info;
            if (cache.TryGetValue(document, out info))
            {
                return info;
            }

            info = await generator(document, cancellationToken).ConfigureAwait(false);
            if (info != null)
            {
                return cache.GetValue(document, _ => info);
            }

            // alright, we don't have cached information, re-calculate them here.
            var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false);

            // okay, persist this info
            await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false);
            await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false);

            info = selector(data);
            return cache.GetValue(document, _ => info);
        }

        public static Task<SyntaxTreeContextInfo> GetContextInfoAsync(Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(document, s_contextSnapshotCache, SyntaxTreeContextInfo.LoadAsync, tuple => tuple.Item2, cancellationToken);
        }

        public static Task<SyntaxTreeIdentifierInfo> GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(document, s_identifierSnapshotCache, SyntaxTreeIdentifierInfo.LoadAsync, tuple => tuple.Item1, cancellationToken);
        }

        private static Func<Document, CancellationToken, Task<SyntaxTreeDeclarationInfo>> s_loadAsync = SyntaxTreeDeclarationInfo.LoadAsync;
        private static Func<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>, SyntaxTreeDeclarationInfo> s_getThirdItem = tuple => tuple.Item3;

        public static Task<SyntaxTreeDeclarationInfo> GetDeclarationInfoAsync(
            Document document, CancellationToken cancellationToken)
        {
            return GetInfoAsync(
                document, s_declaredSymbolsSnapshotCache, s_loadAsync, 
                s_getThirdItem, cancellationToken);
        }

        // The probability of getting a false positive when calling ContainsIdentifier.
        private const double FalsePositiveProbability = 0.0001;

        private static async Task<ValueTuple<SyntaxTreeIdentifierInfo, SyntaxTreeContextInfo, SyntaxTreeDeclarationInfo>> CreateInfoAsync(Document document, CancellationToken cancellationToken)
        {
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
            var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive;
            var isCaseSensitive = !ignoreCase;

            HashSet<string> identifiers;
            HashSet<string> escapedIdentifiers;
            GetIdentifierSet(ignoreCase, out identifiers, out escapedIdentifiers);

            try
            {
                var containsForEachStatement = false;
                var containsLockStatement = false;
                var containsUsingStatement = false;
                var containsQueryExpression = false;
                var containsThisConstructorInitializer = false;
                var containsBaseConstructorInitializer = false;
                var containsElementAccess = false;
                var containsIndexerMemberCref = false;

                var predefinedTypes = (int)PredefinedType.None;
                var predefinedOperators = (int)PredefinedOperator.None;

                var declaredSymbolInfos = new List<DeclaredSymbolInfo>();

{'fraction_non_alphanumeric': 0.06784242178525576, 'fraction_numerical': 0.0021623082640718967, 'mean_word_length': 2.9561614541566428, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3229101', 'n_tokens_mistral': 3433, 'n_tokens_neox': 3349, 'n_words': 831}