Skip to content

Commit

Permalink
?. and ?[ operators now use the existing GenerateNullableTypeConversi…
Browse files Browse the repository at this point in the history
…on that ensures a nullable type isn't converted to a nullable of nullable.

Fixes dynamicexpresso#169
  • Loading branch information
metoule committed Oct 19, 2021
1 parent ce4dc65 commit 229b5c1
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 8 deletions.
10 changes: 2 additions & 8 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -651,10 +651,7 @@ private Expression ParsePrimary()
NextToken();

// ?. operator changes value types to nullable types
var memberAccess = ParseMemberAccess(null, expr);
if (memberAccess.Type.IsValueType)
memberAccess = PromoteExpression(memberAccess, typeof(Nullable<>).MakeGenericType(memberAccess.Type), true);

var memberAccess = GenerateNullableTypeConversion(ParseMemberAccess(null, expr));
expr = GenerateConditional(GenerateEqual(expr, ParserConstants.NullLiteralExpression), ParserConstants.NullLiteralExpression, memberAccess, _token.pos);
}
else if (_token.id == TokenId.OpenBracket)
Expand All @@ -664,10 +661,7 @@ private Expression ParsePrimary()
else if (_token.id == TokenId.QuestionOpenBracket)
{
// ?[ operator changes value types to nullable types
var elementAccess = ParseElementAccess(expr);
if (elementAccess.Type.IsValueType)
elementAccess = PromoteExpression(elementAccess, typeof(Nullable<>).MakeGenericType(elementAccess.Type), true);

var elementAccess = GenerateNullableTypeConversion(ParseElementAccess(expr));
expr = GenerateConditional(GenerateEqual(expr, ParserConstants.NullLiteralExpression), ParserConstants.NullLiteralExpression, elementAccess, _token.pos);
}
else if (_token.id == TokenId.OpenParen)
Expand Down
88 changes: 88 additions & 0 deletions test/DynamicExpresso.UnitTest/GithubIssues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,5 +242,93 @@ public void GitHub_Issue_164()
Assert.AreEqual(str?.Length, interpreter.Eval("str?.Length"));
Assert.AreEqual(str?.Length == 0, interpreter.Eval<bool>("str?.Length == 0"));
}

[Test]
public void GitHub_Issue_164_bis()
{
var interpreter = new Interpreter();

var lambda = interpreter.Parse("Scope?.ValueInt", new Parameter("Scope", typeof(Scope)));

var result = lambda.Invoke((Scope)null);
Assert.IsNull(result);

result = lambda.Invoke(new Scope { ValueInt = 5 });
Assert.AreEqual(5, result);

interpreter.SetVariable("scope", new Scope { ValueInt = 5 });
var resultNullableBool = interpreter.Eval<bool>("scope?.ValueInt?.HasValue");
Assert.IsTrue(resultNullableBool);

// must throw, because scope.ValueInt is not a nullable type
Assert.Throws<ParseException>(() => interpreter.Eval<bool>("scope.ValueInt.HasValue"));
}

private class Scope
{
public int ValueInt { get; set; }
public int? Value { get; set; }

public int[] ArrInt { get; set; }
public int?[] Arr { get; set; }
}

[Test]
public void GitHub_Issue_169()
{
var interpreter = new Interpreter();

var lambda = interpreter.Parse("Scope?.Value", new Parameter("Scope", typeof(Scope)));

var result = lambda.Invoke((Scope)null);
Assert.IsNull(result);

result = lambda.Invoke(new Scope());
Assert.IsNull(result);

result = lambda.Invoke(new Scope { Value = null });
Assert.IsNull(result);

result = lambda.Invoke(new Scope { Value = 5 });
Assert.AreEqual(5, result);
}

[Test]
public void GitHub_Issue_169_bis()
{
var interpreter = new Interpreter();

var lambda = interpreter.Parse("Scope?.Arr?[0]", new Parameter("Scope", typeof(Scope)));

var result = lambda.Invoke(new Scope());
Assert.IsNull(result);

result = lambda.Invoke(new Scope { Arr = null });
Assert.IsNull(result);

result = lambda.Invoke(new Scope { Arr = new int?[] { 5 } });
Assert.AreEqual(5, result);
}

[Test]
public void GitHub_Issue_169_ter()
{
var interpreter = new Interpreter();

var lambda = interpreter.Parse("Scope?.ArrInt?[0]", new Parameter("Scope", typeof(Scope)));

var result = lambda.Invoke(new Scope());
Assert.IsNull(result);

result = lambda.Invoke(new Scope { ArrInt = new int[] { 5 } });
Assert.AreEqual(5, result);

interpreter.SetVariable("scope", new Scope { ArrInt = new int[] { 5 } });
var resultNullableBool = interpreter.Eval<bool>("scope?.ArrInt?[0].HasValue");
Assert.IsTrue(resultNullableBool);

// must throw, because scope.ValueInt is not a nullable type
Assert.Throws<ParseException>(() => interpreter.Eval<bool>("scope.ArrInt[0].HasValue"));
}
}
}

0 comments on commit 229b5c1

Please sign in to comment.