Skip to content

Commit 55dc8dc

Browse files
committed
#167 - доработка статического анализа на проверку неоднозначных вызовов
1 parent 645547e commit 55dc8dc

File tree

7 files changed

+86
-1
lines changed

7 files changed

+86
-1
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using HydraScript.Domain.IR.Impl.Symbols.Ids;
3+
4+
namespace HydraScript.Application.StaticAnalysis.Exceptions;
5+
6+
[ExcludeFromCodeCoverage]
7+
public class AmbiguousInvocation(
8+
string segment,
9+
IReadOnlyCollection<FunctionSymbolId> candidates) :
10+
SemanticException(
11+
segment,
12+
$"Candidates are:\n{string.Join('\n', candidates)}");
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using HydraScript.Domain.IR.Impl.Symbols.Ids;
2+
3+
namespace HydraScript.Application.StaticAnalysis;
4+
5+
public interface IAmbiguousInvocationStorage
6+
{
7+
void WriteCandidate(FunctionSymbolId invocation, FunctionSymbolId candidate);
8+
9+
void CheckCandidatesAndThrow(string segment, FunctionSymbolId invocation);
10+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using HydraScript.Application.StaticAnalysis.Exceptions;
2+
using HydraScript.Domain.IR.Impl.Symbols.Ids;
3+
4+
namespace HydraScript.Application.StaticAnalysis.Impl;
5+
6+
internal class AmbiguousInvocationStorage : IAmbiguousInvocationStorage
7+
{
8+
private readonly Dictionary<FunctionSymbolId, HashSet<FunctionSymbolId>> _invocations = [];
9+
10+
public void WriteCandidate(FunctionSymbolId invocation, FunctionSymbolId candidate)
11+
{
12+
if (!_invocations.ContainsKey(invocation))
13+
_invocations[invocation] = [];
14+
_invocations[invocation].Add(candidate);
15+
}
16+
17+
public void CheckCandidatesAndThrow(string segment, FunctionSymbolId invocation)
18+
{
19+
var candidates = _invocations.GetValueOrDefault(invocation, []);
20+
if (candidates.Count > 0)
21+
throw new AmbiguousInvocation(segment, candidates);
22+
}
23+
}

src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public static IServiceCollection AddStaticAnalysis(this IServiceCollection servi
2121
services.AddSingleton<IJavaScriptTypesProvider, JavaScriptTypesProvider>();
2222
services.AddSingleton<IDefaultValueForTypeCalculator, DefaultValueForTypeCalculator>();
2323

24+
services.AddSingleton<IAmbiguousInvocationStorage, AmbiguousInvocationStorage>();
25+
2426
services.AddSingleton<IVisitor<TypeValue, Type>, TypeBuilder>();
2527

2628
services.AddSingleton<IVisitor<IAbstractSyntaxTreeNode>, SymbolTableInitializer>();

src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ internal class DeclarationVisitor : VisitorNoReturnBase<IAbstractSyntaxTreeNode>
1717
private readonly IFunctionWithUndefinedReturnStorage _functionStorage;
1818
private readonly IMethodStorage _methodStorage;
1919
private readonly ISymbolTableStorage _symbolTables;
20+
private readonly IAmbiguousInvocationStorage _ambiguousInvocations;
2021
private readonly IVisitor<TypeValue, Type> _typeBuilder;
2122

2223
public DeclarationVisitor(
2324
IFunctionWithUndefinedReturnStorage functionStorage,
2425
IMethodStorage methodStorage,
2526
ISymbolTableStorage symbolTables,
27+
IAmbiguousInvocationStorage ambiguousInvocations,
2628
IVisitor<TypeValue, Type> typeBuilder)
2729
{
2830
_functionStorage = functionStorage;
2931
_methodStorage = methodStorage;
3032
_symbolTables = symbolTables;
33+
_ambiguousInvocations = ambiguousInvocations;
3134
_typeBuilder = typeBuilder;
3235
}
3336

@@ -128,6 +131,12 @@ public VisitUnit Visit(FunctionDeclaration visitable)
128131
{
129132
parentTable.AddSymbol(existing, overload);
130133
}
134+
135+
if (existing is not null && !existing.Id.Equals(overload))
136+
{
137+
_ambiguousInvocations.WriteCandidate(overload, existing.Id);
138+
_ambiguousInvocations.WriteCandidate(overload, functionSymbolId);
139+
}
131140
}
132141

133142
return visitable.Statements.Accept(This);

src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ internal class SemanticChecker : VisitorBase<IAbstractSyntaxTreeNode, Type>,
4646
private readonly IMethodStorage _methodStorage;
4747
private readonly ISymbolTableStorage _symbolTables;
4848
private readonly IComputedTypesStorage _computedTypes;
49+
private readonly IAmbiguousInvocationStorage _ambiguousInvocations;
4950
private readonly IVisitor<TypeValue, Type> _typeBuilder;
5051

5152
public SemanticChecker(
@@ -54,13 +55,15 @@ public SemanticChecker(
5455
IMethodStorage methodStorage,
5556
ISymbolTableStorage symbolTables,
5657
IComputedTypesStorage computedTypes,
58+
IAmbiguousInvocationStorage ambiguousInvocations,
5759
IVisitor<TypeValue, Type> typeBuilder)
5860
{
5961
_calculator = calculator;
6062
_functionStorage = functionStorage;
6163
_methodStorage = methodStorage;
6264
_symbolTables = symbolTables;
6365
_computedTypes = computedTypes;
66+
_ambiguousInvocations = ambiguousInvocations;
6467
_typeBuilder = typeBuilder;
6568
}
6669

@@ -411,13 +414,15 @@ public Type Visit(CallExpression visitable)
411414
var objectType = (ObjectType)visitable.Member.Accept(This);
412415
var availableMethods = _methodStorage.GetAvailableMethods(objectType);
413416
var methodKey = new FunctionSymbolId(objectType.LastAccessedMethodName, [objectType, ..parameters]);
417+
_ambiguousInvocations.CheckCandidatesAndThrow(visitable.Segment, methodKey);
414418
functionSymbol =
415419
availableMethods.GetValueOrDefault(methodKey)
416420
?? throw new UnknownFunctionOverload(visitable.Id, methodKey);
417421
}
418422
else
419423
{
420424
var functionKey = new FunctionSymbolId(visitable.Id, parameters);
425+
_ambiguousInvocations.CheckCandidatesAndThrow(visitable.Segment, functionKey);
421426
functionSymbol =
422427
_symbolTables[visitable.Scope].FindSymbol(functionKey)
423428
?? throw new UnknownFunctionOverload(visitable.Id, functionKey);

tests/HydraScript.IntegrationTests/ErrorPrograms/DefaultParameterTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,30 @@ public void DefaultParameter_PlacedBeforeNamed_HydraScriptError()
1212
var code = runner.Invoke();
1313
code.Should().Be(Executor.ExitCodes.HydraScriptError);
1414
fixture.LogMessages.Should()
15-
.Contain(x => x.Contains("The argument b: boolean of function func is placed after default value argument"));
15+
.Contain(x =>
16+
x.Contains("The argument b: boolean of function func is placed after default value argument"));
17+
}
18+
19+
[Fact]
20+
public void DefaultParameter_AmbiguousInvocation_HydraScriptError()
21+
{
22+
const string script =
23+
"""
24+
function f(a = 0){}
25+
function f(a = 0, b = 1) {}
26+
function f(a = 0, b = 1, c = 2) {}
27+
28+
f()
29+
"""
30+
;
31+
using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script));
32+
var code = runner.Invoke();
33+
code.Should().Be(Executor.ExitCodes.HydraScriptError);
34+
const string expectedMessage =
35+
"Candidates are:\n" +
36+
"function f(number)\n" +
37+
"function f(number, number)\n" +
38+
"function f(number, number, number)";
39+
fixture.LogMessages.Should().Contain(x => x.Contains(expectedMessage));
1640
}
1741
}

0 commit comments

Comments
 (0)