Skip to content

Commit

Permalink
ParametrizedCode: fix recursive assignment in parameter default values
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi authored and tomasherceg committed Oct 30, 2024
1 parent b587cdd commit e4eca58
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 27 deletions.
81 changes: 56 additions & 25 deletions src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,25 @@ public ParametrizedCode AssignParameters(Func<CodeSymbolicParameter, CodeParamet
if (stringParts == null) return this;
Debug.Assert(parameters is object);

var assignment = FindAssignment(parameterAssignment, optional: true, allIsDefault: out bool allIsDefault);
var (assignment, newDefaults) = FindAssignment(parameterAssignment);

if (allIsDefault) return this;
if (assignment is null)
{
if (newDefaults is null)
return this;
else
{
// just change the content of parameters, no need to rebuild strings
var newParams = parameters.AsSpan().ToArray();
for (int i = 0; i < newParams.Length; i++)
{
ref var p = ref newParams[i];
if (newDefaults[i] is { } newDefault)
p = new CodeParameterInfo(p.Parameter, p.OperatorPrecedence, p.IsSafeMemberAccess, newDefault);
}
return new ParametrizedCode(stringParts, newParams, OperatorPrecedence);
}
}

// PERF: reduce allocations here, used at runtime
var builder = new Builder();
Expand All @@ -150,18 +166,11 @@ public ParametrizedCode AssignParameters(Func<CodeSymbolicParameter, CodeParamet
if (a.Code == null) // not assigned by `parameterAssignment`
{
// assign recursively in the default assignment
if (param.DefaultAssignment.Code is {})
{
var newDefault = param.DefaultAssignment.Code.AssignParameters(parameterAssignment);
if (newDefault != param.DefaultAssignment.Code)
builder.Add(new CodeParameterInfo(param.Parameter, param.OperatorPrecedence, param.IsSafeMemberAccess, newDefault));
else
builder.Add(param);
}
if (newDefaults is {} && newDefaults[i] is { } newDefault)
builder.Add(new CodeParameterInfo(param.Parameter, param.OperatorPrecedence, param.IsSafeMemberAccess, newDefault));
else
{
builder.Add(param);
}

builder.Add(stringParts[1 + i]);
}
else
Expand Down Expand Up @@ -201,35 +210,57 @@ public void CopyTo(Builder builder)

private (CodeParameterAssignment parameter, string code)[] FindStringAssignment(Func<CodeSymbolicParameter, CodeParameterAssignment> parameterAssigner, out bool allIsDefault)
{
var pp = FindAssignment(parameterAssigner, optional: false, allIsDefault: out allIsDefault);
allIsDefault = true;
var codes = new(CodeParameterAssignment parameter, string code)[parameters!.Length];
for (int i = 0; i < parameters.Length; i++)
{
codes[i] = (pp[i], pp[i].Code!.ToString(parameterAssigner, out bool allIsDefault_local));
var assignment = parameterAssigner(parameters[i].Parameter);
if (assignment.Code == null)
{
assignment = parameters[i].DefaultAssignment;
if (assignment.Code == null)
throw new InvalidOperationException($"Assignment of parameter '{parameters[i].Parameter}' was not found.");
}
else
allIsDefault = false;

codes[i] = (assignment, assignment.Code!.ToString(parameterAssigner, out bool allIsDefault_local));
allIsDefault &= allIsDefault_local;
}
return codes;
}

private CodeParameterAssignment[] FindAssignment(Func<CodeSymbolicParameter, CodeParameterAssignment> parameterAssigner, bool optional, out bool allIsDefault)
private (CodeParameterAssignment[]? assigned, ParametrizedCode?[]? newDefaults) FindAssignment(Func<CodeSymbolicParameter, CodeParameterAssignment> parameterAssigner)
{
allIsDefault = true;
var pp = new CodeParameterAssignment[parameters!.Length];
if (parameters is null)
return (null, null);

// these are different variables, as we have to preserve the tree-like structure of the ParametrizedCodes,
// when we assign parameters in the default values.
// newDefaults -> we will change the code in the parameters[i].DefaultAssignment
// assigned -> this parameter will be removed and its assignment inlined into stringParts[i] and parameters[i]
CodeParameterAssignment[]? assigned = null; // when null, all are default
ParametrizedCode[]? newDefaults = null; // when null, no defaults were changed
for (int i = 0; i < parameters.Length; i++)
{
if ((pp[i] = parameterAssigner(parameters[i].Parameter)).Code == null)
var p = parameterAssigner(parameters[i].Parameter);
if (p.Code is not null)
{
assigned ??= new CodeParameterAssignment[parameters.Length];
assigned[i] = p;
}
else if (parameters[i].DefaultAssignment is { Code: { HasParameters: true } } defaultAssignment)
{
if (!optional)
// check if the default assignment contains any of the assigned parameters, and adjust the default if necessary
var newDefault = defaultAssignment.Code.AssignParameters(parameterAssigner);
if (newDefault != defaultAssignment.Code)
{
pp[i] = parameters[i].DefaultAssignment;
if (pp[i].Code == null)
throw new InvalidOperationException($"Assignment of parameter '{parameters[i].Parameter}' was not found.");
newDefaults ??= new ParametrizedCode[parameters.Length];
newDefaults[i] = newDefault;
}
}
else
allIsDefault = false;
}
return pp;
return (assigned, newDefaults);
}

public IEnumerable<CodeSymbolicParameter> EnumerateAllParameters()
Expand Down
3 changes: 2 additions & 1 deletion src/Tests/ControlTests/PostbackHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public async Task ButtonHandlers()
<!-- suppress postback -->
<dot:Button DataContext={value: Nested} Click={staticCommand: 0} Text="Test supress">
<Postback.Handlers>
<dot:SuppressPostBackHandler Suppress={value: _parent.Integer > 100 || SomeString.Length < 5} />
<dot:SuppressPostBackHandler Suppress={value: _parent.Integer > 100} />
<dot:SuppressPostBackHandler Suppress={value: SomeString.Length < 5} />
</Postback.Handlers>
</dot:Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<!-- ko with: Nested -->
<input onclick="dotvvm.applyPostbackHandlers((options) => {
0;
},this,[[&quot;suppress&quot;,(c,d)=>({suppress:c.$parent.Integer() > 100 || d.SomeString()?.length < 5})]]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;" type="button" value="Test supress">
},this,[[&quot;suppress&quot;,(c,d)=>({suppress:c.$parent.Integer() > 100})],[&quot;suppress&quot;,(c,d)=>({suppress:d.SomeString()?.length < 5})]]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;" type="button" value="Test supress">
<!-- /ko -->
<!-- confirm -->
<!-- ko with: Nested -->
Expand Down

0 comments on commit e4eca58

Please sign in to comment.