Исправление перекомпиляции в Intellisense, если транзитивные зависимости были перекомпилированы (#3414)

* Add invalidating dependent modules in code completion parser controller

* Copy fields of importedModuleScope into fictive modules for SPython

* Copy changes in code completion parser controller to linux version

* Refactor CodeCompletionParserController

* Delete cache clearing in CodeCompletionParserController when invalidating dependent modules
This commit is contained in:
Александр Земляк 2026-04-03 23:08:57 +03:00 committed by GitHub
parent 97eb1132bb
commit 67060ad9ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 277 additions and 132 deletions

View file

@ -3107,7 +3107,11 @@ namespace CodeCompletion
if (fictiveUnit == null)
{
fictiveUnit = new InterfaceUnitScope(new SymInfo(importedModuleName + "?ModuleName", SymbolKind.Namespace, ""), null);
fictiveUnit = new InterfaceUnitScope(new SymInfo(importedModuleName + "?ModuleName", SymbolKind.Namespace, ""), null);
// TODO: настроить копирование EVA
fictiveUnit.file_name = importedModuleScope.file_name;
fictiveUnit.used_units = importedModuleScope.used_units;
((InterfaceUnitScope)fictiveUnit).impl_scope = ((InterfaceUnitScope)importedModuleScope).impl_scope;
currentScope.AddUsedUnit(fictiveUnit);
}
@ -3139,6 +3143,10 @@ namespace CodeCompletion
if (fictiveUnit == null)
{
fictiveUnit = new InterfaceUnitScope(new SymInfo(importedModuleName, SymbolKind.Namespace, ""), null);
// TODO: настроить копирование EVA
fictiveUnit.file_name = importedModuleScope.file_name;
fictiveUnit.used_units = importedModuleScope.used_units;
((InterfaceUnitScope)fictiveUnit).impl_scope = ((InterfaceUnitScope)importedModuleScope).impl_scope;
currentScope.AddUsedUnit(fictiveUnit);
}

View file

@ -536,6 +536,55 @@ namespace CodeCompletion
used_units.Add(unit);
}
/// <summary>
/// Получить массив всех транзитивно используемых модулей (ислючая пр-ва имен).
/// По умолчанию для InterfaceUnitScope ищутся и зависимости реализации тоже.
/// </summary>
public SymScope[] GetRealUsedUnitsTransitive(bool includeImplementationDependenciesForUnit = true)
{
var usedUnits = new List<SymScope>();
if (used_units != null)
{
usedUnits.AddRange(used_units.Where(unit => unit.file_name != null));
}
if (this is InterfaceUnitScope interfaceScope && includeImplementationDependenciesForUnit && interfaceScope.impl_scope != null)
{
foreach (var recursiveUsedUnit in interfaceScope.impl_scope.GetRealUsedUnitsTransitive(true))
{
if (recursiveUsedUnit == this)
continue;
if (usedUnits.FirstOrDefault(unit => unit.file_name == recursiveUsedUnit.file_name) == null)
{
usedUnits.Add(recursiveUsedUnit);
}
}
}
if (used_units != null)
{
foreach (var usedUnit in used_units)
{
var recursiveUsedUnits = usedUnit.GetRealUsedUnitsTransitive(includeImplementationDependenciesForUnit);
foreach (var recursiveUsedUnit in recursiveUsedUnits)
{
if (recursiveUsedUnit == this)
continue;
if (usedUnits.FirstOrDefault(unit => unit.file_name == recursiveUsedUnit.file_name) == null)
{
usedUnits.Add(recursiveUsedUnit);
}
}
}
}
return usedUnits.ToArray();
}
public virtual string GetFullName()
{
return si.name;

View file

@ -143,94 +143,58 @@ namespace VisualPascalABC
{
try
{
Dictionary<string, string> recomp_files = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
HashSet<string> recomp_files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
bool is_comp = false;
foreach (string FileName in filesToParse.Keys.ToArray()) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
{
if (filesToParse[FileName])
{
// Попытка компиляции была
filesToParse[FileName] = false;
is_comp = true;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
if (string.IsNullOrEmpty(text))
text = "begin end.";
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
CodeCompletion.DomConverter dc = controller.Compile(FileName, text);
mem_delta += Environment.WorkingSet - cur_mem;
filesToParse[FileName] = false;
if (dc.is_compiled)
if (CompileWatchedFile(FileName, text, true))
{
//CodeCompletion.CodeCompletionController.comp_modules.Remove(file_name);
if (tmp != null && tmp.visitor.entry_scope != null)
{
tmp.visitor.entry_scope.Clear();
if (tmp.visitor.cur_scope != null)
tmp.visitor.cur_scope.Clear();
}
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
recomp_files[FileName] = FileName;
filesToParse[FileName] = false;
if (ParseInformationUpdated != null)
ParseInformationUpdated(dc.visitor.entry_scope, FileName);
// успешная компиляция
recomp_files.Add(FileName);
}
else if (CodeCompletion.CodeCompletionController.comp_modules[FileName] == null)
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
}
}
foreach (string FileName in filesToParse.Keys.ToArray()) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
{
CodeCompletion.DomConverter dc = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
CodeCompletion.SymScope ss = null;
if (dc != null)
{
if (dc.visitor.entry_scope != null) ss = dc.visitor.entry_scope;
else if (dc.visitor.impl_scope != null) ss = dc.visitor.impl_scope;
int j = 0;
while (j < 2)
CodeCompletion.SymScope watchedUnitScope = dc.visitor.entry_scope;
if (watchedUnitScope != null)
{
if (j == 0)
var usedUnitsTransitive = watchedUnitScope.GetRealUsedUnitsTransitive();
for (int i = 0; i < usedUnitsTransitive.Length; i++)
{
ss = dc.visitor.entry_scope;
j++;
}
else
{
ss = dc.visitor.impl_scope;
j++;
}
if (ss != null)
{
for (int i = 0; i < ss.used_units.Count; i++)
string usedUnitFileName = usedUnitsTransitive[i].file_name;
// Если какая-то из зависимостей была перекомпилирована
if (recomp_files.Contains(usedUnitFileName))
{
string s = ss.used_units[i].file_name;
if (s != null && filesToParse.ContainsKey(s) && recomp_files.ContainsKey(s))
// Помечаем нужные модули для будущей перекомпиляции
InvalidateDependentModules(usedUnitsTransitive, usedUnitFileName, filesToParse, FileName);
// Перекомпилируем текущий модуль
is_comp = true;
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
// Здесь третий параметр false, потому что старые данные компиляции еще могут пригодиться до новой перекомпиляции EVA
if (CompileWatchedFile(FileName, text, false))
{
is_comp = true;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
dc = controller.Compile(FileName, text);
mem_delta += Environment.WorkingSet - cur_mem;
filesToParse[FileName] = false;
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
if (dc.is_compiled)
{
/*if (tmp != null && tmp.stv.entry_scope != null)
{
tmp.stv.entry_scope.Clear();
if (tmp.stv.cur_scope != null) tmp.stv.cur_scope.Clear();
}*/
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
recomp_files[FileName] = FileName;
ss.used_units[i] = dc.visitor.entry_scope;
if (ParseInformationUpdated != null)
ParseInformationUpdated(dc.visitor.entry_scope, FileName);
}
else if (CodeCompletion.CodeCompletionController.comp_modules[FileName] == null)
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
// успешная компиляция
recomp_files.Add(FileName);
}
}
}
@ -248,6 +212,86 @@ namespace VisualPascalABC
}
/// <summary>
/// Вызов компиляции (интеллисенсом) файла с именем fileName и содержимым fileText.
/// Возвращает true, если компиляция была успешна, false в противном случае.
/// </summary>
private bool CompileWatchedFile(string fileName, string fileText, bool clearOldScope)
{
bool success = false;
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[fileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
CodeCompletion.DomConverter dc = controller.Compile(fileName, fileText);
mem_delta += Environment.WorkingSet - cur_mem;
if (dc.is_compiled)
{
if (clearOldScope && tmp != null)
{
tmp.visitor.entry_scope?.Clear();
tmp.visitor.cur_scope?.Clear();
}
CodeCompletion.CodeCompletionController.comp_modules[fileName] = dc;
success = true;
ParseInformationUpdated?.Invoke(dc.visitor.entry_scope, fileName);
}
else if (tmp == null)
CodeCompletion.CodeCompletionController.comp_modules[fileName] = dc;
return success;
}
/// <summary>
/// Помечает модули из candidateModulesToCheck и watchedFiles для будущей перекомпиляции, если они завивисимы от модуля с именем recompiledDependencyName
/// </summary>
private void InvalidateDependentModules(CodeCompletion.SymScope[] candidateModulesToCheck, string recompiledDependencyName, Dictionary<string, bool> watchedFiles, string currentFileForRecompiling)
{
// Скоупы модулей, соответствующих watchedFiles
var compiledWatchedScopes = watchedFiles.Where(watchedFile => watchedFile.Key != currentFileForRecompiling)
.Select(watchedFile => CodeCompletion.CodeCompletionController.comp_modules[watchedFile.Key])
.SelectMany(converter => converter is CodeCompletion.DomConverter dc ?
new CodeCompletion.SymScope[] { dc.visitor.entry_scope, dc.visitor.impl_scope }
: Enumerable.Empty<CodeCompletion.SymScope>())
.Where(sc => sc != null);
var scopesToCheck = compiledWatchedScopes.Concat(candidateModulesToCheck).Distinct();
foreach (var scope in scopesToCheck)
{
string scopeFileName = scope.file_name;
if (scope is CodeCompletion.ImplementationUnitScope)
scopeFileName = ((CodeCompletion.SymScope)scope.TopScope).file_name;
if (scopeFileName == recompiledDependencyName)
continue;
var usedUnitsForUnit = scope.GetRealUsedUnitsTransitive();
// Если в зависимостях скоупа есть recompiledDependency
if (usedUnitsForUnit.FirstOrDefault(u => u.file_name == recompiledDependencyName) != null)
{
var unitOldConverter = CodeCompletion.CodeCompletionController.comp_modules[scopeFileName];
if (unitOldConverter != null)
{
// Помечаем для перекомпиляции
if (watchedFiles.ContainsKey(scopeFileName))
{
watchedFiles[scopeFileName] = true;
}
else
{
CodeCompletion.CodeCompletionController.comp_modules.Remove(scopeFileName);
}
}
}
}
}
public bool IsParsing()
{
return th != null && th.ThreadState == System.Threading.ThreadState.Running;

View file

@ -148,94 +148,58 @@ namespace VisualPascalABC
{
try
{
Dictionary<string, string> recomp_files = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
HashSet<string> recomp_files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
bool is_comp = false;
foreach (string FileName in filesToParse.Keys.ToArray()) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
{
if (filesToParse[FileName])
{
// Попытка компиляции была
filesToParse[FileName] = false;
is_comp = true;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
if (string.IsNullOrEmpty(text))
text = "begin end.";
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
CodeCompletion.DomConverter dc = controller.Compile(FileName, text);
mem_delta += Environment.WorkingSet - cur_mem;
filesToParse[FileName] = false;
if (dc.is_compiled)
if (CompileWatchedFile(FileName, text, true))
{
//CodeCompletion.CodeCompletionController.comp_modules.Remove(file_name);
if (tmp != null && tmp.visitor.entry_scope != null)
{
tmp.visitor.entry_scope.Clear();
if (tmp.visitor.cur_scope != null)
tmp.visitor.cur_scope.Clear();
}
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
recomp_files[FileName] = FileName;
filesToParse[FileName] = false;
if (ParseInformationUpdated != null)
ParseInformationUpdated(dc.visitor.entry_scope, FileName);
// успешная компиляция
recomp_files.Add(FileName);
}
else if (CodeCompletion.CodeCompletionController.comp_modules[FileName] == null)
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
}
}
foreach (string FileName in filesToParse.Keys.ToArray()) // копирование ключей обязательно, иначе будет InvalidOperationException EVA
{
CodeCompletion.DomConverter dc = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
CodeCompletion.SymScope ss = null;
if (dc != null)
{
if (dc.visitor.entry_scope != null) ss = dc.visitor.entry_scope;
else if (dc.visitor.impl_scope != null) ss = dc.visitor.impl_scope;
int j = 0;
while (j < 2)
CodeCompletion.SymScope watchedUnitScope = dc.visitor.entry_scope;
if (watchedUnitScope != null)
{
if (j == 0)
var usedUnitsTransitive = watchedUnitScope.GetRealUsedUnitsTransitive();
for (int i = 0; i < usedUnitsTransitive.Length; i++)
{
ss = dc.visitor.entry_scope;
j++;
}
else
{
ss = dc.visitor.impl_scope;
j++;
}
if (ss != null)
{
for (int i = 0; i < ss.used_units.Count; i++)
string usedUnitFileName = usedUnitsTransitive[i].file_name;
// Если какая-то из зависимостей была перекомпилирована
if (recomp_files.Contains(usedUnitFileName))
{
string s = ss.used_units[i].file_name;
if (s != null && filesToParse.ContainsKey(s) && recomp_files.ContainsKey(s))
// Помечаем нужные модули для будущей перекомпиляции
InvalidateDependentModules(usedUnitsTransitive, usedUnitFileName, filesToParse, FileName);
// Перекомпилируем текущий модуль
is_comp = true;
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
// Здесь третий параметр false, потому что старые данные компиляции еще могут пригодиться до новой перекомпиляции EVA
if (CompileWatchedFile(FileName, text, false))
{
is_comp = true;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
string text = visualEnvironmentCompiler.SourceFilesProvider(FileName, SourceFileOperation.GetText) as string;
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[FileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
dc = controller.Compile(FileName, text);
mem_delta += Environment.WorkingSet - cur_mem;
filesToParse[FileName] = false;
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
if (dc.is_compiled)
{
/*if (tmp != null && tmp.stv.entry_scope != null)
{
tmp.stv.entry_scope.Clear();
if (tmp.stv.cur_scope != null) tmp.stv.cur_scope.Clear();
}*/
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
recomp_files[FileName] = FileName;
ss.used_units[i] = dc.visitor.entry_scope;
if (ParseInformationUpdated != null)
ParseInformationUpdated(dc.visitor.entry_scope, FileName);
}
else if (CodeCompletion.CodeCompletionController.comp_modules[FileName] == null)
CodeCompletion.CodeCompletionController.comp_modules[FileName] = dc;
// успешная компиляция
recomp_files.Add(FileName);
}
}
}
@ -253,6 +217,86 @@ namespace VisualPascalABC
}
/// <summary>
/// Вызов компиляции (интеллисенсом) файла с именем fileName и содержимым fileText.
/// Возвращает true, если компиляция была успешна, false в противном случае.
/// </summary>
private bool CompileWatchedFile(string fileName, string fileText, bool clearOldScope)
{
bool success = false;
CodeCompletion.DomConverter tmp = CodeCompletion.CodeCompletionController.comp_modules[fileName] as CodeCompletion.DomConverter;
long cur_mem = Environment.WorkingSet;
CodeCompletion.CodeCompletionController controller = new CodeCompletion.CodeCompletionController();
CodeCompletion.DomConverter dc = controller.Compile(fileName, fileText);
mem_delta += Environment.WorkingSet - cur_mem;
if (dc.is_compiled)
{
if (clearOldScope && tmp != null)
{
tmp.visitor.entry_scope?.Clear();
tmp.visitor.cur_scope?.Clear();
}
CodeCompletion.CodeCompletionController.comp_modules[fileName] = dc;
success = true;
ParseInformationUpdated?.Invoke(dc.visitor.entry_scope, fileName);
}
else if (tmp == null)
CodeCompletion.CodeCompletionController.comp_modules[fileName] = dc;
return success;
}
/// <summary>
/// Помечает модули из candidateModulesToCheck и watchedFiles для будущей перекомпиляции, если они завивисимы от модуля с именем recompiledDependencyName
/// </summary>
private void InvalidateDependentModules(CodeCompletion.SymScope[] candidateModulesToCheck, string recompiledDependencyName, Dictionary<string, bool> watchedFiles, string currentFileForRecompiling)
{
// Скоупы модулей, соответствующих watchedFiles
var compiledWatchedScopes = watchedFiles.Where(watchedFile => watchedFile.Key != currentFileForRecompiling)
.Select(watchedFile => CodeCompletion.CodeCompletionController.comp_modules[watchedFile.Key])
.SelectMany(converter => converter is CodeCompletion.DomConverter dc ?
new CodeCompletion.SymScope[] { dc.visitor.entry_scope, dc.visitor.impl_scope }
: Enumerable.Empty<CodeCompletion.SymScope>())
.Where(sc => sc != null);
var scopesToCheck = compiledWatchedScopes.Concat(candidateModulesToCheck).Distinct();
foreach (var scope in scopesToCheck)
{
string scopeFileName = scope.file_name;
if (scope is CodeCompletion.ImplementationUnitScope)
scopeFileName = ((CodeCompletion.SymScope)scope.TopScope).file_name;
if (scopeFileName == recompiledDependencyName)
continue;
var usedUnitsForUnit = scope.GetRealUsedUnitsTransitive();
// Если в зависимостях скоупа есть recompiledDependency
if (usedUnitsForUnit.FirstOrDefault(u => u.file_name == recompiledDependencyName) != null)
{
var unitOldConverter = CodeCompletion.CodeCompletionController.comp_modules[scopeFileName];
if (unitOldConverter != null)
{
// Помечаем для перекомпиляции
if (watchedFiles.ContainsKey(scopeFileName))
{
watchedFiles[scopeFileName] = true;
}
else
{
CodeCompletion.CodeCompletionController.comp_modules.Remove(scopeFileName);
}
}
}
}
}
public bool IsParsing()
{
return th != null && th.ThreadState == System.Threading.ThreadState.Running;