Skip to content

Commit

Permalink
Added additional check to ensure we can't have duplicate parameter na…
Browse files Browse the repository at this point in the history
…mes in a child lambda scope.
  • Loading branch information
Holden Mai authored and Holden Mai committed Sep 20, 2022
1 parent 2b33655 commit f190b40
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 31 deletions.
46 changes: 36 additions & 10 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -138,7 +139,7 @@ private Expression ParseLambdaExpression()
{
// in case the expression is not a lambda, we have to restart parsing
var originalPos = _token.pos;

var isLambda = false;
try
{
var parameters = ParseLambdaParameterList();
Expand Down Expand Up @@ -168,10 +169,15 @@ private Expression ParseLambdaExpression()
}

var lambdaBodyExp = _expressionText.Substring(startExpr, _token.pos - startExpr);
isLambda = true;
return new InterpreterExpression(_arguments, lambdaBodyExp, parameters);
}
catch (ParseException)
{
if (isLambda)
{
throw;
}
// not a lambda, return to the saved position
SetTextPos(originalPos);
NextToken();
Expand All @@ -180,13 +186,24 @@ private Expression ParseLambdaExpression()
}
}

private Parameter[] ParseLambdaParameterList()
private class ParameterWithPosition : Parameter
{
public ParameterWithPosition(int pos, string name, Type type)
: base(name, type)
{
Position = pos;
}

public int Position { get; }
}

private ParameterWithPosition[] ParseLambdaParameterList()
{
var hasOpenParen = _token.id == TokenId.OpenParen;
if (hasOpenParen)
NextToken();

var parameters = _token.id != TokenId.CloseParen ? ParseLambdaParameters() : new Parameter[0];
var parameters = _token.id != TokenId.CloseParen ? ParseLambdaParameters() : new ParameterWithPosition[0];
if (hasOpenParen)
{
ValidateToken(TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected);
Expand All @@ -196,9 +213,9 @@ private Parameter[] ParseLambdaParameterList()
return parameters;
}

private Parameter[] ParseLambdaParameters()
private ParameterWithPosition[] ParseLambdaParameters()
{
var argList = new List<Parameter>();
var argList = new List<ParameterWithPosition>();
while (true)
{
argList.Add(ParseLambdaParameter());
Expand All @@ -208,11 +225,12 @@ private Parameter[] ParseLambdaParameters()
return argList.ToArray();
}

private Parameter ParseLambdaParameter()
private ParameterWithPosition ParseLambdaParameter()
{
ValidateToken(TokenId.Identifier);
var name = _token.text;

var pos = _token.pos;
if (TryParseKnownType(name, out var type))
{
ValidateToken(TokenId.Identifier);
Expand All @@ -224,7 +242,7 @@ private Parameter ParseLambdaParameter()
}

NextToken();
return new Parameter(name, type);
return new ParameterWithPosition(pos, name, type);
}

// = operator
Expand Down Expand Up @@ -1229,7 +1247,6 @@ private MemberBinding[] ParseMemberInitializerList(Type newType)
var member = FindPropertyOrField(newType, propertyOrFieldName, false);
if (member == null)
throw CreateParseException(_token.pos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, GetTypeName(newType));

NextToken();

ValidateToken(TokenId.Equal, ErrorMessages.EqualExpected);
Expand Down Expand Up @@ -3249,9 +3266,10 @@ private class InterpreterExpression : Expression
private readonly IList<Parameter> _parameters;
private Type _type;

public InterpreterExpression(ParserArguments parserArguments, string expressionText, params Parameter[] parameters)
public InterpreterExpression(ParserArguments parserArguments, string expressionText, params ParameterWithPosition[] parameters)
{
_interpreter = new Interpreter(parserArguments.Settings.Clone());
var settings = parserArguments.Settings.Clone();
_interpreter = new Interpreter(settings);
_expressionText = expressionText;
_parameters = parameters;

Expand All @@ -3265,6 +3283,14 @@ public InterpreterExpression(ParserArguments parserArguments, string expressionT
_interpreter.SetIdentifier(new Identifier(dp.Name, pe));
}

foreach (var myParameter in parameters)
{
if (settings.Identifiers.ContainsKey(myParameter.Name))
{
throw new ParseException($"A local or parameter named '{myParameter.Name}' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter", myParameter.Position);
}
}

// prior to evaluation, we don't know the generic arguments types
_type = typeof(Func<>).Assembly.GetType($"System.Func`{parameters.Length + 1}");
}
Expand Down
85 changes: 64 additions & 21 deletions test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,70 @@ public string GetChildrenIdentifiers<T>(Func<NestedLambdaTestClass, T> f)
[Test]
public void Lambda_WithMultipleNestedExpressions()
{
NestedLambdaTestClass root = new NestedLambdaTestClass()
var root = BuildNestedTestClassHierarchy();
var expectedResult = root.GetChildrenIdentifiers(
// root
l1 => l1.Name + l1.GetChildrenIdentifiers(
// level 2, references my parameter, plus original lamda
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(
// level 3, references my parameter, plus parameter from l1 lamda
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
// level 4, references my parameter, plus all parameters that have been used
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)));

var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions);

var evalResult = target.Eval<string>(@"root.GetChildrenIdentifiers(
l1 => l1.Name + l1.GetChildrenIdentifiers(
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)))", new Parameter(nameof(root), root));
Assert.AreEqual(expectedResult, evalResult);
}

[Test]
public void Lambda_SameParameterNameInDifferentLambdas()
{
var root = BuildNestedTestClassHierarchy();
var expectedResult = root.GetChildrenIdentifiers(
// root
l1 => l1.Name + l1.GetChildrenIdentifiers(
// level 2, references my parameter, plus original lamda
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l3 => l2.Name) + l2.GetChildrenIdentifiers(
// level 3, references my parameter, plus parameter from l1 lamda
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
// level 4, references my parameter, plus all parameters that have been used
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)));

var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions);

var evalResult = target.Eval<string>(@"root.GetChildrenIdentifiers(
l1 => l1.Name + l1.GetChildrenIdentifiers(
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l3 => l2.Name) + + l2.GetChildrenIdentifiers(
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)))", new Parameter(nameof(root), root));
Assert.AreEqual(expectedResult, evalResult);
}

[Test]
public void Lambda_CannotUseDuplicateParameterInSubLambda()
{
var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions);
Assert.Throws<ParseException>(() => target.Parse(@"root.GetChildrenIdentifiers(
l1 => l1.Name + l1.GetChildrenIdentifiers(
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(l2 => l2.Name) + l2.GetChildrenIdentifiers(
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)))", new Parameter("root", typeof(NestedLambdaTestClass))));
}

private static NestedLambdaTestClass BuildNestedTestClassHierarchy()
{
return new NestedLambdaTestClass()
{
Name = "Root",
Children = new List<NestedLambdaTestClass>()
Expand Down Expand Up @@ -522,26 +585,6 @@ public void Lambda_WithMultipleNestedExpressions()
}
}
};
var expectedResult = root.GetChildrenIdentifiers(
// root
l1 => l1.Name + l1.GetChildrenIdentifiers(
// level 2, references my parameter, plus original lamda
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(
// level 3, references my parameter, plus parameter from l1 lamda
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
// level 4, references my parameter, plus all parameters that have been used
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)));

var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions);

var evalResult = target.Eval<string>(@"root.GetChildrenIdentifiers(
l1 => l1.Name + l1.GetChildrenIdentifiers(
l2 => l2.Name + l1.Name + l2.GetChildrenIdentifiers(
l3 => l3.Name + l2.Name + l3.GetChildrenIdentifiers(
l4 => l4.Name + l2.Name + l3.Name + l1.Name + root.Name)
)))", new Parameter(nameof(root), root));
Assert.AreEqual(expectedResult, evalResult);
}
}

Expand Down

0 comments on commit f190b40

Please sign in to comment.