Skip to content

Commit 1433fdc

Browse files
committed
#202 - static analysis
1 parent f4aa604 commit 1433fdc

File tree

8 files changed

+101
-7
lines changed

8 files changed

+101
-7
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions;
3+
4+
namespace HydraScript.Application.StaticAnalysis.Exceptions;
5+
6+
[ExcludeFromCodeCoverage]
7+
public class ExplicitCastNotSupported(CastAsExpression cast, Type from, Type to) :
8+
SemanticException(cast.Segment, $"Cast from {from} to {to} is not supported");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace HydraScript.Application.StaticAnalysis;
2+
3+
public interface IExplicitCastValidator
4+
{
5+
bool IsAllowed(Type from, Type to);
6+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using HydraScript.Domain.IR.Types;
2+
3+
namespace HydraScript.Application.StaticAnalysis.Impl;
4+
5+
internal sealed class ExplicitCastValidator : IExplicitCastValidator
6+
{
7+
private static readonly Type Boolean = "boolean";
8+
private static readonly Type Number = "number";
9+
private static readonly Type String = "string";
10+
private static readonly Any Any = new();
11+
12+
private readonly Dictionary<Type, List<Type>> _allowedConversions = new()
13+
{
14+
{ String, [Any] },
15+
{ Number, [String, Boolean] },
16+
{ Boolean, [String, Number] },
17+
};
18+
19+
public bool IsAllowed(Type from, Type to)
20+
{
21+
var typeEqualityComparer = default(CommutativeTypeEqualityComparer);
22+
23+
if (typeEqualityComparer.Equals(from, to))
24+
return true;
25+
26+
if (!_allowedConversions.TryGetValue(to, out var allowedFrom))
27+
return false;
28+
29+
for (var i = 0; i < allowedFrom.Count; i++)
30+
{
31+
if (typeEqualityComparer.Equals(allowedFrom[i], from))
32+
return true;
33+
}
34+
35+
return false;
36+
}
37+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static IServiceCollection AddStaticAnalysis(this IServiceCollection servi
2121
services.AddSingleton<IStandardLibraryProvider, StandardLibraryProvider>();
2222
services.AddSingleton<IJavaScriptTypesProvider, JavaScriptTypesProvider>();
2323
services.AddSingleton<IDefaultValueForTypeCalculator, DefaultValueForTypeCalculator>();
24+
services.AddSingleton<IExplicitCastValidator, ExplicitCastValidator>();
2425

2526
services.AddSingleton<IAmbiguousInvocationStorage, AmbiguousInvocationStorage>();
2627

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ internal class SemanticChecker : VisitorBase<IAbstractSyntaxTreeNode, Type>,
5050
private readonly ISymbolTableStorage _symbolTables;
5151
private readonly IComputedTypesStorage _computedTypes;
5252
private readonly IAmbiguousInvocationStorage _ambiguousInvocations;
53+
private readonly IExplicitCastValidator _explicitCastValidator;
5354
private readonly IVisitor<TypeValue, Type> _typeBuilder;
5455

5556
public SemanticChecker(
@@ -59,6 +60,7 @@ public SemanticChecker(
5960
ISymbolTableStorage symbolTables,
6061
IComputedTypesStorage computedTypes,
6162
IAmbiguousInvocationStorage ambiguousInvocations,
63+
IExplicitCastValidator explicitCastValidator,
6264
IVisitor<TypeValue, Type> typeBuilder)
6365
{
6466
_calculator = calculator;
@@ -67,6 +69,7 @@ public SemanticChecker(
6769
_symbolTables = symbolTables;
6870
_computedTypes = computedTypes;
6971
_ambiguousInvocations = ambiguousInvocations;
72+
_explicitCastValidator = explicitCastValidator;
7073
_typeBuilder = typeBuilder;
7174
}
7275

@@ -84,7 +87,7 @@ public Type Visit(ScriptBody visitable)
8487
_symbolTables.Clear();
8588
_computedTypes.Clear();
8689
_ambiguousInvocations.Clear();
87-
90+
8891
return "undefined";
8992
}
9093

@@ -433,14 +436,16 @@ public ObjectType Visit(WithExpression visitable)
433436
public Type Visit(CastAsExpression visitable)
434437
{
435438
Type undefined = "undefined";
436-
var exprType = visitable.Expression.Accept(This);
439+
var from = visitable.Expression.Accept(This);
437440

438-
if (exprType.Equals(undefined))
441+
if (from.Equals(undefined))
439442
throw new CannotDefineType(visitable.Expression.Segment);
440443

441-
return visitable.Cast.Accept(_typeBuilder) == "string"
442-
? "string"
443-
: throw new NotSupportedException("Other types but 'string' have not been supported for casting yet");
444+
var to = visitable.Cast.Accept(_typeBuilder);
445+
446+
return _explicitCastValidator.IsAllowed(from, to)
447+
? to
448+
: throw new ExplicitCastNotSupported(visitable, from, to);
444449
}
445450

446451
public Type Visit(CallExpression visitable)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace HydraScript.Domain.IR.Types;
2+
3+
public readonly ref struct CommutativeTypeEqualityComparer : IEqualityComparer<Type>
4+
{
5+
public bool Equals(Type? x, Type? y) =>
6+
x?.Equals(y) != false || y?.Equals(x) != false;
7+
8+
public int GetHashCode(Type obj) => obj.GetHashCode();
9+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using HydraScript.Application.StaticAnalysis.Impl;
2+
using HydraScript.Domain.IR.Types;
3+
4+
namespace HydraScript.UnitTests.Application;
5+
6+
public class ExplicitCastValidatorTests
7+
{
8+
private readonly ExplicitCastValidator _explicitCastValidator = new();
9+
10+
[Theory, MemberData(nameof(ConversionsData))]
11+
public void IsAllowed_Always_Success(Type from, Type to, bool expected) =>
12+
_explicitCastValidator.IsAllowed(from, to).Should().Be(expected);
13+
14+
public static TheoryData<Type, Type, bool> ConversionsData =>
15+
new()
16+
{
17+
{ new ObjectType([]), "string", true },
18+
{ "number", new NullableType("number"), true },
19+
{ new NullableType("number"), "number", true },
20+
{ "string", "number", true },
21+
{ "string", "boolean", true },
22+
{ "boolean", "number", true },
23+
{ "number", "boolean", true },
24+
{ new ObjectType([]), "boolean", false },
25+
{ new ArrayType("number"), "number", false },
26+
{ new ArrayType("number"), new NullableType("boolean"), false },
27+
};
28+
}

tests/HydraScript.UnitTests/Domain/BackEnd/InstructionsData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public InstructionsData()
1919
{
2020
Left = Name("str")
2121
},
22-
"str = num as string");
22+
"str = num as String");
2323
Add(
2424
new BeginBlock(BlockType.Function, blockId: "func")
2525
{

0 commit comments

Comments
 (0)