Skip to content

Commit ace888d

Browse files
authored
#56 - запретит присваивания null в ситуациях неопределённого типа (#124)
* #56 - доработка ChildOf: условие для родителя * #56 - запреты на присваивание null * #56 - тесты
1 parent 890aa92 commit ace888d

File tree

8 files changed

+71
-9
lines changed

8 files changed

+71
-9
lines changed

samples/linkedlist.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function append(lst: list, item: number) {
1616

1717
function getOdd(lst: list): number[] {
1818
let result: number[]
19-
let n = lst.head
19+
let n: node = lst.head
2020
while (n != null) {
2121
if (n.data % 2 != 0) {
2222
let i = n.data
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace HydraScript.Application.StaticAnalysis.Exceptions;
4+
5+
[ExcludeFromCodeCoverage]
6+
public class CannotAssignNullWhenUndefined(string segment) :
7+
SemanticException(segment, "Cannot assign 'null' when type is undefined");

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public Type Visit(ArrayLiteral visitable)
157157
if (visitable.Expressions.Count == 0)
158158
return new ArrayType(new Any());
159159

160-
var type = visitable.First().Accept(This);
160+
var type = visitable[0].Accept(This);
161161
if (visitable.Expressions.All(e => e.Accept(This).Equals(type)))
162162
return new ArrayType(type);
163163

@@ -169,6 +169,14 @@ public Type Visit(ObjectLiteral visitable)
169169
var properties = visitable.Properties.Select(prop =>
170170
{
171171
var propType = prop.Expression.Accept(This);
172+
173+
if (propType is NullType &&
174+
(visitable.ChildOf<AssignmentExpression>(
175+
x => x.ChildOf<LexicalDeclaration>() && x.DestinationType is null) ||
176+
visitable.ChildOf<ReturnStatement>(x => x.ChildOf<FunctionDeclaration>(
177+
decl => decl.ReturnTypeValue is TypeIdentValue { TypeId.Name: "undefined" }))))
178+
throw new CannotAssignNullWhenUndefined(prop.Segment);
179+
172180
var propSymbol = propType switch
173181
{
174182
ObjectType objectType => new ObjectSymbol(prop.Id, objectType),
@@ -279,6 +287,8 @@ public Type Visit(LexicalDeclaration visitable)
279287
assignment.Segment,
280288
left: registeredSymbol.Type,
281289
right: sourceType);
290+
if (sourceType is NullType && registeredSymbol.Type.Equals(undefined))
291+
throw new CannotAssignNullWhenUndefined(assignment.Segment);
282292

283293
var actualType = registeredSymbol.Type.Equals(undefined)
284294
? sourceType
@@ -480,6 +490,9 @@ public Type Visit(FunctionDeclaration visitable)
480490
if (!symbol.Type.Equals(@void) && !hasReturnStatement)
481491
throw new FunctionWithoutReturnStatement(visitable.Segment);
482492

493+
if (symbol.Type is NullType)
494+
throw new CannotAssignNullWhenUndefined(visitable.Segment);
495+
483496
return symbol.Type;
484497
}
485498

src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,22 @@ public IReadOnlyList<IAbstractSyntaxTreeNode> GetAllNodes()
4141
return result;
4242
}
4343

44-
public bool ChildOf<T>() where T : IAbstractSyntaxTreeNode
44+
/// <summary>
45+
/// Метод возвращает <c>true</c>, если узел - потомок заданного типа и выполняется заданное условие.<br/>
46+
/// В случае, когда условие не задано, проверяется просто соответствие типов.
47+
/// </summary>
48+
/// <param name="condition">Условие для родителя</param>
49+
/// <typeparam name="T">Проверяемый тип родителя</typeparam>
50+
public bool ChildOf<T>(Predicate<T>? condition = null) where T : IAbstractSyntaxTreeNode
4551
{
4652
var parent = Parent;
4753
while (parent != default!)
4854
{
49-
if (parent is T)
55+
if (parent is T node)
5056
{
51-
return true;
57+
return condition is not null
58+
? condition(node)
59+
: true;
5260
}
5361

5462
parent = parent.Parent;

src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ private ObjectLiteral ObjectLiteral()
721721

722722
Expect("Colon");
723723
var expr = Expression();
724-
properties.Add(new Property(id, expr));
724+
properties.Add(new Property(id, expr) { Segment = idToken.Segment });
725725

726726
Expect("SemiColon");
727727
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.CommandLine.Parsing;
2+
3+
namespace HydraScript.IntegrationTests.ErrorPrograms;
4+
5+
public class NullAssignmentWhenUndefinedTests(TestHostFixture fixture) : IClassFixture<TestHostFixture>
6+
{
7+
[Theory, MemberData(nameof(NullAssignmentScripts))]
8+
public void NullAssignment_UndefinedDestinationOrReturnType_HydraScriptError(string script)
9+
{
10+
var runner = fixture.GetRunner(
11+
configureTestServices: services =>
12+
services.SetupInMemoryScript(script));
13+
var code = runner.Invoke(fixture.InMemoryScript);
14+
code.Should().Be(ExitCodes.HydraScriptError);
15+
fixture.LogMessages.Should()
16+
.Contain(x => x.Contains("Cannot assign 'null' when type is undefined"));
17+
}
18+
19+
public static TheoryData<string> NullAssignmentScripts
20+
{
21+
get
22+
{
23+
const string lexicalDeclaration = "let x = null";
24+
const string objectLiteralProperty = "let y = {prop: null;}";
25+
const string functionReturn = "function f() { return null }";
26+
return new TheoryData<string>([
27+
lexicalDeclaration,
28+
objectLiteralProperty,
29+
functionReturn]);
30+
}
31+
}
32+
}

tests/HydraScript.IntegrationTests/ErrorPrograms/VariableInitializationTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace HydraScript.IntegrationTests.ErrorPrograms;
55
public class VariableInitializationTests(TestHostFixture fixture) : IClassFixture<TestHostFixture>
66
{
77
[Theory, MemberData(nameof(VariableInitializationScripts))]
8-
public void VariableWithoutTypeDeclared_AccessedBeforeInitialization_ExitCodeHydraScriptError(string script)
8+
public void VariableWithoutTypeDeclared_AccessedBeforeInitialization_HydraScriptError(string script)
99
{
1010
var runner = fixture.GetRunner(
1111
configureTestServices: services =>
@@ -36,7 +36,9 @@ function f() {
3636
return 5
3737
}
3838
""";
39-
return new TheoryData<string>([variableWithoutTypeDeclared, typedVariableDeclared]);
39+
return new TheoryData<string>([
40+
variableWithoutTypeDeclared,
41+
typedVariableDeclared]);
4042
}
4143
}
4244
}

tests/HydraScript.IntegrationTests/ErrorPrograms/VoidAssignmentTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace HydraScript.IntegrationTests.ErrorPrograms;
55
public class VoidAssignmentTests(TestHostFixture fixture) : IClassFixture<TestHostFixture>
66
{
77
[Fact]
8-
public void FunctionDeclared_VoidReturnAssigned_ExitCodeHydraScriptError()
8+
public void FunctionDeclared_VoidReturnAssigned_HydraScriptError()
99
{
1010
const string script =
1111
"""

0 commit comments

Comments
 (0)