Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into compilation-page-warn…
Browse files Browse the repository at this point in the history
…ings
  • Loading branch information
exyi committed Feb 8, 2024
2 parents c42dd8e + 33a7fec commit f056894
Show file tree
Hide file tree
Showing 34 changed files with 538 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public DefaultExpressionToDelegateCompiler(DotvvmConfiguration config)
interpret = config.Debug;
}
public Delegate Compile(LambdaExpression expression) =>
interpret ? expression.Compile(preferInterpretation: interpret) :
// the interpreter is broken: https://github.com/dotnet/runtime/issues/96385
// interpret ? expression.Compile(preferInterpretation: interpret) :
expression.Compile();
// TODO: use FastExpressionCompiler
// we can't do that atm since it still has some bugs, when these are fixed we should use that for all bindings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,28 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
public bool HasParamsAttribute { get; set; }
}

private Expression GetDefaultValue(ParameterInfo parameter)
{
if (parameter.HasDefaultValue)
{
var value = parameter.DefaultValue;
if (value is null && parameter.ParameterType.IsValueType)
{
// null with struct type means `default(T)`
value = ReflectionUtils.GetDefaultValue(parameter.ParameterType);
}
return Expression.Constant(value, parameter.ParameterType);
}
else if (parameter.IsDefined(ParamArrayAttributeType))
{
return Expression.NewArrayInit(parameter.ParameterType.GetElementType().NotNull());
}
else
{
throw new Exception($"Internal error: parameter {parameter.Name} of method {parameter.Member.Name} does not have a default value.");
}
}

private MethodRecognitionResult? TryCallMethod(MethodInfo method, Type[]? typeArguments, Expression[] positionalArguments, IDictionary<string, Expression>? namedArguments)
{
if (positionalArguments.Contains(null)) throw new ArgumentNullException("positionalArguments[]");
Expand Down Expand Up @@ -445,7 +467,7 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
if (typeArgs[genericArgumentPosition] == null)
{
// try to resolve from arguments
var argType = GetGenericParameterType(genericArguments[genericArgumentPosition], parameterTypes, args.Select(s => s.Type).ToArray());
var argType = GetGenericParameterType(genericArguments[genericArgumentPosition], parameterTypes, args.Select(s => s?.Type).ToArray());
automaticTypeArgs++;
if (argType != null) typeArgs[genericArgumentPosition] = argType;
else return null;
Expand All @@ -466,11 +488,15 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
}
else if (typeArguments != null) return null;

// cast arguments
// cast arguments and fill defaults
for (int i = 0; i < args.Length; i++)
{
if (args[i] == null)
{
args[i] = GetDefaultValue(parameters[i]);
}
Type elm;
if (args.Length == i + 1 && hasParamsArrayAttributes && !args[i].Type.IsArray)
if (args.Length == i + 1 && hasParamsArrayAttributes && !args[i]!.Type.IsArray)
{
elm = parameters[i].ParameterType.GetElementType().NotNull();
if (positionalArguments.Skip(i).Any(s => TypeConversion.ImplicitConversion(s, elm) is null))
Expand All @@ -482,7 +508,7 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
{
elm = parameters[i].ParameterType;
}
var casted = TypeConversion.ImplicitConversion(args[i], elm);
var casted = TypeConversion.ImplicitConversion(args[i]!, elm);
if (casted == null)
{
return null;
Expand All @@ -492,7 +518,7 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
castCount++;
args[i] = casted;
}
if (args.Length == i + 1 && hasParamsArrayAttributes && !args[i].Type.IsArray)
if (args.Length == i + 1 && hasParamsArrayAttributes && !args[i]!.Type.IsArray)
{
var converted = positionalArguments.Skip(i)
.Select(a => TypeConversion.EnsureImplicitConversion(a, elm))
Expand All @@ -505,13 +531,13 @@ public MethodRecognitionResult(int automaticTypeArgCount, int castCount, Express
automaticTypeArgCount: automaticTypeArgs,
castCount: castCount,
method: method,
arguments: args,
arguments: args!,
paramsArrayCount: positionalArguments.Length - args.Length,
hasParamsAttribute: hasParamsArrayAttributes,
isExtension: false
);
}
private static bool TryPrepareArguments(ParameterInfo[] parameters, Expression[] positionalArguments, IDictionary<string, Expression>? namedArguments, [MaybeNullWhen(false)] out Expression[] arguments, out int castCount)
private static bool TryPrepareArguments(ParameterInfo[] parameters, Expression[] positionalArguments, IDictionary<string, Expression>? namedArguments, [MaybeNullWhen(false)] out Expression?[] arguments, out int castCount)
{
castCount = 0;
arguments = null;
Expand All @@ -522,16 +548,17 @@ private static bool TryPrepareArguments(ParameterInfo[] parameters, Expression[]
if (!hasParamsArrayAttribute && parameters.Length < positionalArguments.Length)
return false;

arguments = new Expression[parameters.Length];
arguments = new Expression?[parameters.Length];
var copyItemsCount = !hasParamsArrayAttribute ? positionalArguments.Length : parameters.Length;

if (hasParamsArrayAttribute && parameters.Length > positionalArguments.Length)
{
var parameter = parameters.Last();
var elementType = parameter.ParameterType.GetElementType().NotNull();

// User specified no arguments for the `params` array, we need to create an empty array
arguments[arguments.Length - 1] = Expression.NewArrayInit(elementType);
// User specified no arguments for the `params` array => use default value
// created later by the GetDefaultValue, after we know the generic arguments
arguments[arguments.Length - 1] = null;

// Last argument was just generated => do not copy
addedArguments++;
Expand Down Expand Up @@ -561,7 +588,7 @@ private static bool TryPrepareArguments(ParameterInfo[] parameters, Expression[]
else if (parameters[i].HasDefaultValue)
{
castCount++;
arguments[i] = Expression.Constant(parameters[i].DefaultValue, parameters[i].ParameterType);
arguments[i] = null;
}
else if (parameters[i].IsDefined(ParamArrayAttributeType))
{
Expand All @@ -577,29 +604,30 @@ private static bool TryPrepareArguments(ParameterInfo[] parameters, Expression[]
return true;
}

private Type? GetGenericParameterType(Type genericArg, Type[] searchedGenericTypes, Type[] expressionTypes)
private Type? GetGenericParameterType(Type genericArg, Type[] searchedGenericTypes, Type?[] expressionTypes)
{
for (var i = 0; i < searchedGenericTypes.Length; i++)
{
if (expressionTypes.Length <= i) return null;
var expression = expressionTypes[i];
if (expression == null) continue;
var sgt = searchedGenericTypes[i];
if (sgt == genericArg)
{
return expressionTypes[i];
return expression;
}
if (sgt.IsArray)
{
var elementType = sgt.GetElementType();
var expressionElementType = expressionTypes[i].GetElementType();
var expressionElementType = expression.GetElementType();
if (elementType == genericArg)
return expressionElementType;
else
return GetGenericParameterType(genericArg, searchedGenericTypes[i].GetGenericArguments(), expressionTypes[i].GetGenericArguments());
return GetGenericParameterType(genericArg, searchedGenericTypes[i].GetGenericArguments(), expression.GetGenericArguments());
}
else if (sgt.IsGenericType)
{
Type[]? genericArguments = null;
var expression = expressionTypes[i];

if (expression.IsArray)
{
Expand Down
5 changes: 3 additions & 2 deletions src/Framework/Framework/Compilation/CompiledAssemblyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,13 @@ private HashSet<string> GetAllNamespaces()
foreach (var a in GetAllAssemblies())
{
string? lastNs = null; // namespaces come in batches, usually, so no need to hash it everytime when a quick compare says it's the same as last time
foreach (var type in a.ExportedTypes)
foreach (var type in a.GetLoadableTypes())
{
var ns = type.Namespace;
if (ns is null || lastNs == ns)
continue;
result.Add(ns);
result.Add(ns);
lastNs = ns;
}
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ private IAbstractControl ProcessObjectElement(DothtmlElementNode element, IDataC
constructorParameters = new[] { element.FullTagName };
element.AddError($"The control <{element.FullTagName}> could not be resolved! Make sure that the tagPrefix is registered in DotvvmConfiguration.Markup.Controls collection!");
}
if (controlMetadata.VirtualPath is {} && controlMetadata.Type.IsAssignableTo(ResolvedTypeDescriptor.Create(typeof(DotvvmView))))
{
element.TagNameNode.AddWarning($"The markup control <{element.FullTagName}> has a baseType DotvvmView. Please make sure that the control file has .dotcontrol file extension. This will work, but causes unexpected issues, for example @js directive will not work in this control.");
}
var control = treeBuilder.BuildControl(controlMetadata, element, dataContext);
control.ConstructorParameters = constructorParameters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using DotVVM.Framework.Compilation.Binding;
using System.Collections.Immutable;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Utils;

namespace DotVVM.Framework.Compilation.ControlTree.Resolved
{
Expand Down Expand Up @@ -59,7 +60,7 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache,

public object? ResolvePropertyInitializer(DothtmlDirectiveNode directive, Type propertyType, BindingParserNode? initializer, ImmutableList<NamespaceImport> imports)
{
if (initializer == null) { return CreateDefaultValue(propertyType); }
if (initializer == null) { return ReflectionUtils.GetDefaultValue(propertyType); }

var registry = RegisterImports(TypeRegistry.DirectivesDefault(compiledAssemblyCache), imports);

Expand All @@ -75,25 +76,16 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache,
var lambda = Expression.Lambda<Func<object?>>(Expression.Block(Expression.Convert(TypeConversion.EnsureImplicitConversion(initializerExpression, propertyType), typeof(object))));
var lambdaDelegate = lambda.Compile(true);

return lambdaDelegate.Invoke() ?? CreateDefaultValue(propertyType);
return lambdaDelegate.Invoke() ?? ReflectionUtils.GetDefaultValue(propertyType);
}
catch (Exception ex)
{
directive.AddError("Could not initialize property value.");
directive.AddError(ex.Message);
return CreateDefaultValue(propertyType);
return ReflectionUtils.GetDefaultValue(propertyType);
}
}

private object? CreateDefaultValue(Type? type)
{
if (type != null && type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}

private Expression? CompileDirectiveExpression(DothtmlDirectiveNode directive, BindingParserNode expressionSyntax, ImmutableList<NamespaceImport> imports)
{
TypeRegistry registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public DotvvmCompilationSourceLocation(
{
lineNumber ??= this.Tokens[0].LineNumber;
columnNumber ??= this.Tokens[0].ColumnNumber;
lineErrorLength ??= tokens.Where(t => t.LineNumber == lineNumber).Select(t => (int?)(t.ColumnNumber + t.Length)).LastOrDefault() - columnNumber;
lineErrorLength ??= this.Tokens.Where(t => t.LineNumber == lineNumber).Select(t => (int?)(t.ColumnNumber + t.Length)).LastOrDefault() - columnNumber;
}

this.MarkupFile = markupFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Utils;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Testing;

namespace DotVVM.Framework.Compilation
{
Expand Down Expand Up @@ -164,8 +165,9 @@ public bool BuildView(DotHtmlFileInfo file, bool forceRecompile, out DotHtmlFile

var pageBuilder = controlBuilderFactory.GetControlBuilder(file.VirtualPath);

using var scopedServiceProvider = dotvvmConfiguration.ServiceProvider.CreateScope(); // dependencies that are configured as scoped cannot be resolved from root service provider
var compiledControl = pageBuilder.builder.Value.BuildControl(controlBuilderFactory, scopedServiceProvider.ServiceProvider);
using var scopedServices = dotvvmConfiguration.ServiceProvider.CreateScope(); // dependencies that are configured as scoped cannot be resolved from root service provider
scopedServices.ServiceProvider.GetRequiredService<DotvvmRequestContextStorage>().Context = new ViewCompilationFakeRequestContext(scopedServices.ServiceProvider);
var compiledControl = pageBuilder.builder.Value.BuildControl(controlBuilderFactory, scopedServices.ServiceProvider);

if (pageBuilder.descriptor.MasterPage is { FileName: {} masterPagePath })
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,20 @@ private void AddDefaultDictionaryTranslations()
AddMethodTranslator(() => default(IReadOnlyDictionary<Generic.T, Generic.T>)!.ContainsKey(null!), containsKey);
AddMethodTranslator(() => default(IDictionary<Generic.T, Generic.T>)!.Remove(null!), new GenericMethodCompiler(args =>
new JsIdentifierExpression("dotvvm").Member("translations").Member("dictionary").Member("remove").Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance), args[1])));

var getValueOrDefault = new GenericMethodCompiler((JsExpression[] args, MethodInfo method) => {
var defaultValue =
args.Length > 3 ? args[3] :
new JsLiteral(ReflectionUtils.GetDefaultValue(method.GetGenericArguments().Last()));
return new JsIdentifierExpression("dotvvm").Member("translations").Member("dictionary").Member("getItem").Invoke(args[1], args[2], defaultValue);
});
#if DotNetCore
AddMethodTranslator(() => default(IReadOnlyDictionary<Generic.T, Generic.T>)!.GetValueOrDefault(null!), getValueOrDefault);
AddMethodTranslator(() => default(IReadOnlyDictionary<Generic.T, Generic.T>)!.GetValueOrDefault(null!, null), getValueOrDefault);
#endif
AddMethodTranslator(() => default(IImmutableDictionary<Generic.T, Generic.T>)!.GetValueOrDefault(null!), getValueOrDefault);
AddMethodTranslator(() => default(IImmutableDictionary<Generic.T, Generic.T>)!.GetValueOrDefault(null!, null), getValueOrDefault);
AddMethodTranslator(() => FunctionalExtensions.GetValueOrDefault(default(IReadOnlyDictionary<Generic.T, Generic.T>)!, null!, null!, false), getValueOrDefault);
}

private bool IsDictionary(Type type) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,22 +145,22 @@ private void ReadDirective()
}
SkipWhitespace(false);

if (Peek() is '\r' or '\n' or NullChar)
{
// empty value
CreateToken(DothtmlTokenType.DirectiveValue);
SkipWhitespace();
return;
}

// whitespace
if (LastToken!.Type != DothtmlTokenType.WhiteSpace)
{
CreateToken(DothtmlTokenType.WhiteSpace, errorProvider: t => CreateTokenError(t, DothtmlTokenType.DirectiveStart, DothtmlTokenizerErrors.DirectiveValueExpected));
}

// directive value
if (Peek() == '\r' || Peek() == '\n' || Peek() == NullChar)
{
CreateToken(DothtmlTokenType.DirectiveValue, errorProvider: t => CreateTokenError(t, DothtmlTokenType.DirectiveStart, DothtmlTokenizerErrors.DirectiveValueExpected));
SkipWhitespace();
}
else
{
ReadTextUntilNewLine(DothtmlTokenType.DirectiveValue);
}
ReadTextUntilNewLine(DothtmlTokenType.DirectiveValue);
}

/// <summary>
Expand Down
24 changes: 12 additions & 12 deletions src/Framework/Framework/Controls/AddTemplateDecorator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ public class AddTemplateDecorator: Decorator
{
/// <summary> Template is rendered after the decorated control. </summary>
[MarkupOptions(MappingMode = MappingMode.InnerElement)]
public ITemplate After
public ITemplate AfterTemplate
{
get => (ITemplate)GetValue(AfterProperty)!;
set => SetValue(AfterProperty, value);
get => (ITemplate)GetValue(AfterTemplateProperty)!;
set => SetValue(AfterTemplateProperty, value);
}
public static readonly DotvvmProperty AfterProperty =
DotvvmProperty.Register<ITemplate, AddTemplateDecorator>(nameof(After));
public static readonly DotvvmProperty AfterTemplateProperty =
DotvvmProperty.Register<ITemplate, AddTemplateDecorator>(nameof(AfterTemplate));

/// <summary> Template is rendered before the decorated control. </summary>
[MarkupOptions(MappingMode = MappingMode.InnerElement)]
public ITemplate Before
public ITemplate BeforeTemplate
{
get => (ITemplate)GetValue(BeforeProperty)!;
set => SetValue(BeforeProperty, value);
get => (ITemplate)GetValue(BeforeTemplateProperty)!;
set => SetValue(BeforeTemplateProperty, value);
}
public static readonly DotvvmProperty BeforeProperty =
DotvvmProperty.Register<ITemplate, AddTemplateDecorator>(nameof(Before));
public static readonly DotvvmProperty BeforeTemplateProperty =
DotvvmProperty.Register<ITemplate, AddTemplateDecorator>(nameof(BeforeTemplate));

protected internal override void OnInit(IDotvvmRequestContext context)
{
var after = this.After;
var before = this.Before;
var after = this.AfterTemplate;
var before = this.BeforeTemplate;

if (after is {})
{
Expand Down
Loading

0 comments on commit f056894

Please sign in to comment.