From 1321c7996e81d34f19fc6bf3704e09939960a076 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 10 Jan 2021 10:39:46 +0100 Subject: [PATCH 01/12] Fix various Direct2D resource leak issues, as well as an unclosed geometry stream. --- .../Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 12 +++++++++++- src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs | 6 +++--- .../Media/Imaging/WicBitmapImpl.cs | 6 +++--- .../Avalonia.Direct2D1/Media/StreamGeometryImpl.cs | 9 ++++++--- .../Avalonia.Direct2D1/PrimitiveExtensions.cs | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 136ff63f3d..47a19aad8c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -21,6 +21,7 @@ namespace Avalonia.Direct2D1.Media private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly DeviceContext _deviceContext; + private readonly bool _ownsDeviceContext; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -51,10 +52,12 @@ namespace Avalonia.Direct2D1.Media if (_renderTarget is DeviceContext deviceContext) { _deviceContext = deviceContext; + _ownsDeviceContext = false; } else { _deviceContext = _renderTarget.QueryInterface(); + _ownsDeviceContext = true; } _deviceContext.BeginDraw(); @@ -96,6 +99,13 @@ namespace Avalonia.Direct2D1.Media { throw new RenderTargetCorruptedException(ex); } + finally + { + if (_ownsDeviceContext) + { + _deviceContext.Dispose(); + } + } } /// @@ -151,7 +161,7 @@ namespace Avalonia.Direct2D1.Media using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) - using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D())) + using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 636309ad1a..d04e2b3110 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -33,13 +33,13 @@ namespace Avalonia.Direct2D1.Media /// public IGeometryImpl Intersect(IGeometryImpl geometry) { - var result = new PathGeometry(Geometry.Factory); - + var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory); using (var sink = result.Open()) { Geometry.Combine(((GeometryImpl)geometry).Geometry, CombineMode.Intersect, sink); - return new StreamGeometryImpl(result); + sink.Close(); } + return new StreamGeometryImpl(result); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 49193afd78..90592ea806 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media /// public class WicBitmapImpl : BitmapImpl { - private BitmapDecoder _decoder; + private readonly BitmapDecoder _decoder; private static BitmapInterpolationMode ConvertInterpolationMode(Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) { @@ -41,7 +41,7 @@ namespace Avalonia.Direct2D1.Media /// The filename of the bitmap to load. public WicBitmapImpl(string fileName) { - using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand)) + using (var decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand)) { WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); Dpi = new Vector(96, 96); @@ -177,7 +177,7 @@ namespace Avalonia.Direct2D1.Media /// The Direct2D bitmap. public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory); + using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 9104be64b2..2bc2b2db71 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs @@ -29,9 +29,12 @@ namespace Avalonia.Direct2D1.Media public IStreamGeometryImpl Clone() { var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory); - var sink = result.Open(); - ((PathGeometry)Geometry).Stream(sink); - sink.Close(); + using (var sink = result.Open()) + { + ((PathGeometry)Geometry).Stream(sink); + sink.Close(); + } + return new StreamGeometryImpl(result); } diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index 31e9c260e0..669e139d8f 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -111,7 +111,7 @@ namespace Avalonia.Direct2D1 /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, SharpDX.Direct2D1.RenderTarget renderTarget) { - return pen.ToDirect2DStrokeStyle(renderTarget.Factory); + return pen.ToDirect2DStrokeStyle(Direct2D1Platform.Direct2D1Factory); } /// From afbff15978baef52d2715bc2af4155c0417b1632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 20 Jan 2021 19:47:19 +0000 Subject: [PATCH 02/12] Enabled nullable reference types on Avalonia.Markup. --- .../Avalonia.Markup/Avalonia.Markup.csproj | 1 + src/Markup/Avalonia.Markup/Data/Binding.cs | 16 ++++---- .../Avalonia.Markup/Data/BindingBase.cs | 38 ++++++++----------- .../Avalonia.Markup/Data/MultiBinding.cs | 12 +++--- .../Avalonia.Markup/Data/RelativeSource.cs | 2 +- .../Avalonia.Markup/Data/TemplateBinding.cs | 17 ++++----- .../Parsers/BindingExpressionGrammar.cs | 24 ++++++------ .../Parsers/ExpressionObserverBuilder.cs | 16 ++++---- .../Markup/Parsers/ExpressionParser.cs | 22 +++++------ .../Markup/Parsers/Nodes/ElementNameNode.cs | 2 +- .../Markup/Parsers/Nodes/FindAncestorNode.cs | 6 +-- .../Markup/Parsers/Nodes/StringIndexerNode.cs | 16 ++++---- .../Markup/Parsers/PropertyPathGrammar.cs | 24 ++++++------ .../Markup/Parsers/SelectorGrammar.cs | 20 +++++----- .../Markup/Parsers/SelectorParser.cs | 6 +-- 15 files changed, 108 insertions(+), 114 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 89e2c096ee..ce104b3609 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -2,6 +2,7 @@ netstandard2.0 Avalonia + Enable diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index bf43730481..554c27b85a 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -39,17 +39,17 @@ namespace Avalonia.Data /// /// Gets or sets the name of the element to use as the binding source. /// - public string ElementName { get; set; } + public string? ElementName { get; set; } /// /// Gets or sets the relative source for the binding. /// - public RelativeSource RelativeSource { get; set; } + public RelativeSource? RelativeSource { get; set; } /// /// Gets or sets the source for the binding. /// - public object Source { get; set; } + public object? Source { get; set; } /// /// Gets or sets the binding path. @@ -59,20 +59,21 @@ namespace Avalonia.Data /// /// Gets or sets a function used to resolve types from names in the binding path. /// - public Func TypeResolver { get; set; } + public Func? TypeResolver { get; set; } - protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) + protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation) { - Contract.Requires(target != null); + Contract.Requires(target is not null); anchor = anchor ?? DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; - INameScope nameScope = null; + INameScope? nameScope = null; NameScope?.TryGetTarget(out nameScope); var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope); +#nullable disable if (ElementName != null) { return CreateElementObserver( @@ -138,5 +139,6 @@ namespace Avalonia.Data throw new NotSupportedException(); } } +#nullable enable } } diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 3dbc83a7df..b900a043d7 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -38,22 +38,22 @@ namespace Avalonia.Data /// /// Gets or sets the to use. /// - public IValueConverter Converter { get; set; } + public IValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the value to use when the binding is unable to produce a value. /// - public object FallbackValue { get; set; } + public object? FallbackValue { get; set; } /// /// Gets or sets the value to use when the binding result is null. /// - public object TargetNullValue { get; set; } + public object? TargetNullValue { get; set; } /// /// Gets or sets the binding mode. @@ -68,23 +68,23 @@ namespace Avalonia.Data /// /// Gets or sets the string format. /// - public string StringFormat { get; set; } + public string? StringFormat { get; set; } - public WeakReference DefaultAnchor { get; set; } + public WeakReference? DefaultAnchor { get; set; } - public WeakReference NameScope { get; set; } + public WeakReference? NameScope { get; set; } protected abstract ExpressionObserver CreateExpressionObserver( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor, + object? anchor, bool enableDataValidation); /// public InstancedBinding Initiate( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor = null, + object? anchor = null, bool enableDataValidation = false) { Contract.Requires(target != null); @@ -133,18 +133,13 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node, bool targetIsDataContext, - object anchor) + object? anchor) { Contract.Requires(target != null); if (!(target is IDataContextProvider)) { - target = anchor as IDataContextProvider; - - if (target == null) - { - throw new InvalidOperationException("Cannot find a DataContext to bind to."); - } + target = anchor as IDataContextProvider ?? throw new InvalidOperationException("Cannot find a DataContext to bind to."); } if (!targetIsDataContext) @@ -173,8 +168,7 @@ namespace Avalonia.Data { Contract.Requires(target != null); - NameScope.TryGetTarget(out var scope); - if (scope == null) + if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null) throw new InvalidOperationException("Name scope is null or was already collected"); var result = new ExpressionObserver( NameScopeLocator.Track(scope, elementName), @@ -190,7 +184,7 @@ namespace Avalonia.Data { Contract.Requires(target != null); - IObservable controlLocator; + IObservable controlLocator; switch (relativeSource.Tree) { @@ -229,7 +223,7 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node) { - Contract.Requires(target != null); + Contract.Requires(target is not null); var result = new ExpressionObserver( () => target.GetValue(StyledElement.TemplatedParentProperty), @@ -240,7 +234,7 @@ namespace Avalonia.Data return result; } - protected IObservable GetParentDataContext(IAvaloniaObject target) + protected IObservable GetParentDataContext(IAvaloniaObject target) { // The DataContext is based on the visual parent and not the logical parent: this may // seem counter intuitive considering the fact that property inheritance works on the logical @@ -252,7 +246,7 @@ namespace Avalonia.Data .Select(x => { return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ?? - Observable.Return((object)null); + Observable.Return((object?)null); }).Switch(); } diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index cbc5f414f2..1a33536fa3 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -22,12 +22,12 @@ namespace Avalonia.Data /// /// Gets or sets the to use. /// - public IMultiValueConverter Converter { get; set; } + public IMultiValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the value to use when the binding is unable to produce a value. @@ -52,12 +52,12 @@ namespace Avalonia.Data /// /// Gets or sets the relative source for the binding. /// - public RelativeSource RelativeSource { get; set; } + public RelativeSource? RelativeSource { get; set; } /// /// Gets or sets the string format. /// - public string StringFormat { get; set; } + public string? StringFormat { get; set; } public MultiBinding() { @@ -69,7 +69,7 @@ namespace Avalonia.Data public InstancedBinding Initiate( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor = null, + object? anchor = null, bool enableDataValidation = false) { var targetType = targetProperty?.PropertyType ?? typeof(object); @@ -105,7 +105,7 @@ namespace Avalonia.Data } } - private object ConvertValue(IList values, Type targetType, IMultiValueConverter converter) + private object ConvertValue(IList values, Type targetType, IMultiValueConverter? converter) { for (var i = 0; i < values.Count; ++i) { diff --git a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs index e3d2dd4aaa..037175a137 100644 --- a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs +++ b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs @@ -94,7 +94,7 @@ namespace Avalonia.Data /// /// Gets the type of ancestor to look for when in mode. /// - public Type AncestorType { get; set; } + public Type? AncestorType { get; set; } /// /// Gets or sets a value that describes the type of relative source lookup. diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 83f02c52aa..b6c723d68c 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -17,8 +17,8 @@ namespace Avalonia.Data ISetterValue { private bool _isSetterValue; - private IStyledElement _target; - private Type _targetType; + private IStyledElement _target = default!; + private Type? _targetType; public TemplateBinding() { @@ -33,7 +33,7 @@ namespace Avalonia.Data public InstancedBinding Initiate( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor = null, + object? anchor = null, bool enableDataValidation = false) { // Usually each `TemplateBinding` will only be instantiated once; in this case we can @@ -68,12 +68,12 @@ namespace Avalonia.Data /// /// Gets or sets the to use. /// - public IValueConverter Converter { get; set; } + public IValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the binding mode. @@ -83,7 +83,7 @@ namespace Avalonia.Data /// /// Gets or sets the name of the source property on the templated parent. /// - public AvaloniaProperty Property { get; set; } + public AvaloniaProperty? Property { get; set; } /// public string Description => "TemplateBinding: " + Property; @@ -164,10 +164,7 @@ namespace Avalonia.Data { if (e.Property == StyledElement.TemplatedParentProperty) { - var oldValue = (IAvaloniaObject)e.OldValue; - var newValue = (IAvaloniaObject)e.OldValue; - - if (oldValue != null) + if (e.OldValue is IAvaloniaObject oldValue) { oldValue.PropertyChanged -= TemplatedParentPropertyChanged; } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 7c362e24cc..4da6a7f3ae 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -271,8 +271,8 @@ namespace Avalonia.Markup.Parsers } else if (mode.SequenceEqual("parent".AsSpan())) { - string ancestorNamespace = null; - string ancestorType = null; + string? ancestorNamespace = null; + string? ancestorType = null; var ancestorLevel = 0; if (PeekOpenBracket(ref r)) { @@ -424,19 +424,19 @@ namespace Avalonia.Markup.Parsers public class PropertyNameNode : INode { - public string PropertyName { get; set; } + public string PropertyName { get; set; } = string.Empty; } public class AttachedPropertyNameNode : INode { - public string Namespace { get; set; } - public string TypeName { get; set; } - public string PropertyName { get; set; } + public string Namespace { get; set; } = string.Empty; + public string TypeName { get; set; } = string.Empty; + public string PropertyName { get; set; } = string.Empty; } public class IndexerNode : INode { - public IList Arguments { get; set; } + public IList Arguments { get; set; } = Array.Empty(); } public class NotNode : INode, ITransformNode { } @@ -447,20 +447,20 @@ namespace Avalonia.Markup.Parsers public class NameNode : INode { - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } public class AncestorNode : INode { - public string Namespace { get; set; } - public string TypeName { get; set; } + public string? Namespace { get; set; } + public string? TypeName { get; set; } public int Level { get; set; } } public class TypeCastNode : INode { - public string Namespace { get; set; } - public string TypeName { get; set; } + public string Namespace { get; set; } = string.Empty; + public string TypeName { get; set; } = string.Empty; } } } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index f957bcab1e..e615923ff0 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -8,8 +8,8 @@ namespace Avalonia.Markup.Parsers { public static class ExpressionObserverBuilder { - internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func typeResolver = null, - INameScope nameScope = null) + internal static (ExpressionNode? Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func? typeResolver = null, + INameScope? nameScope = null) { if (string.IsNullOrWhiteSpace(expression)) { @@ -32,8 +32,8 @@ namespace Avalonia.Markup.Parsers object root, string expression, bool enableDataValidation = false, - string description = null, - Func typeResolver = null) + string? description = null, + Func? typeResolver = null) { return new ExpressionObserver( root, @@ -45,8 +45,8 @@ namespace Avalonia.Markup.Parsers IObservable rootObservable, string expression, bool enableDataValidation = false, - string description = null, - Func typeResolver = null) + string? description = null, + Func? typeResolver = null) { Contract.Requires(rootObservable != null); return new ExpressionObserver( @@ -61,8 +61,8 @@ namespace Avalonia.Markup.Parsers string expression, IObservable update, bool enableDataValidation = false, - string description = null, - Func typeResolver = null) + string? description = null, + Func? typeResolver = null) { Contract.Requires(rootGetter != null); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index 558130e23f..0a6f3f82ba 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs @@ -11,25 +11,25 @@ namespace Avalonia.Markup.Parsers internal class ExpressionParser { private readonly bool _enableValidation; - private readonly Func _typeResolver; - private readonly INameScope _nameScope; + private readonly Func? _typeResolver; + private readonly INameScope? _nameScope; - public ExpressionParser(bool enableValidation, Func typeResolver, INameScope nameScope) + public ExpressionParser(bool enableValidation, Func? typeResolver, INameScope? nameScope) { _typeResolver = typeResolver; _nameScope = nameScope; _enableValidation = enableValidation; } - public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r) + public (ExpressionNode? Node, SourceMode Mode) Parse(ref CharacterReader r) { - ExpressionNode rootNode = null; - ExpressionNode node = null; + ExpressionNode? rootNode = null; + ExpressionNode? node = null; var (astNodes, mode) = BindingExpressionGrammar.Parse(ref r); foreach (var astNode in astNodes) { - ExpressionNode nextNode = null; + ExpressionNode? nextNode = null; switch (astNode) { case BindingExpressionGrammar.EmptyExpressionNode _: @@ -57,13 +57,13 @@ namespace Avalonia.Markup.Parsers nextNode = ParseFindAncestor(ancestor); break; case BindingExpressionGrammar.NameNode elementName: - nextNode = new ElementNameNode(_nameScope, elementName.Name); + nextNode = new ElementNameNode(_nameScope ?? throw new NotSupportedException("Invalid element name binding with null name scope!"), elementName.Name); break; case BindingExpressionGrammar.TypeCastNode typeCast: nextNode = ParseTypeCastNode(typeCast); break; } - if (rootNode is null) + if (node is null) { rootNode = node = nextNode; } @@ -79,7 +79,7 @@ namespace Avalonia.Markup.Parsers private FindAncestorNode ParseFindAncestor(BindingExpressionGrammar.AncestorNode node) { - Type ancestorType = null; + Type? ancestorType = null; var ancestorLevel = node.Level; if (!(node.Namespace is null) && !(node.TypeName is null)) @@ -97,7 +97,7 @@ namespace Avalonia.Markup.Parsers private TypeCastNode ParseTypeCastNode(BindingExpressionGrammar.TypeCastNode node) { - Type castType = null; + Type? castType = null; if (!(node.Namespace is null) && !(node.TypeName is null)) { if (_typeResolver == null) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index 97198145a8..d6068a15d4 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Markup.Parsers.Nodes { private readonly WeakReference _nameScope; private readonly string _name; - private IDisposable _subscription; + private IDisposable? _subscription; public ElementNameNode(INameScope nameScope, string name) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs index f304d1e9a2..4124032113 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs @@ -7,10 +7,10 @@ namespace Avalonia.Markup.Parsers.Nodes public class FindAncestorNode : ExpressionNode { private readonly int _level; - private readonly Type _ancestorType; - private IDisposable _subscription; + private readonly Type? _ancestorType; + private IDisposable? _subscription; - public FindAncestorNode(Type ancestorType, int level) + public FindAncestorNode(Type? ancestorType, int level) { _level = level; _ancestorType = ancestorType; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs index f3abd6a5c5..2f1756fc55 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs @@ -29,15 +29,15 @@ namespace Avalonia.Markup.Parsers.Nodes var list = target as IList; var dictionary = target as IDictionary; var indexerProperty = GetIndexer(typeInfo); - var indexerParameters = indexerProperty?.GetIndexParameters(); + ParameterInfo[] indexerParameters; - if (indexerProperty != null && indexerParameters.Length == Arguments.Count) + if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count) { var convertedObjectArray = new object[indexerParameters.Length]; for (int i = 0; i < Arguments.Count; i++) { - object temp = null; + object? temp = null; if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp)) { @@ -125,7 +125,7 @@ namespace Avalonia.Markup.Parsers.Nodes public IList Arguments { get; } - public override Type PropertyType + public override Type? PropertyType { get { @@ -144,15 +144,15 @@ namespace Avalonia.Markup.Parsers.Nodes var list = target as IList; var dictionary = target as IDictionary; var indexerProperty = GetIndexer(typeInfo); - var indexerParameters = indexerProperty?.GetIndexParameters(); + ParameterInfo[] indexerParameters; - if (indexerProperty != null && indexerParameters.Length == Arguments.Count) + if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count) { var convertedObjectArray = new object[indexerParameters.Length]; for (int i = 0; i < Arguments.Count; i++) { - object temp = null; + object? temp = null; if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp)) { @@ -246,7 +246,7 @@ namespace Avalonia.Markup.Parsers.Nodes return true; } - private static PropertyInfo GetIndexer(TypeInfo typeInfo) + private static PropertyInfo? GetIndexer(TypeInfo? typeInfo) { PropertyInfo indexer; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index c5953b514c..8e0a40ccf4 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -30,7 +30,7 @@ namespace Avalonia.Markup.Parsers var parsed = new List(); while (state != State.End) { - ISyntax syntax = null; + ISyntax? syntax = null; if (state == State.Start) (state, syntax) = ParseStart(ref r); else if (state == State.Next) @@ -53,7 +53,7 @@ namespace Avalonia.Markup.Parsers return parsed; } - private static (State, ISyntax) ParseNext(ref CharacterReader r) + private static (State, ISyntax?) ParseNext(ref CharacterReader r) { r.SkipWhitespace(); if (r.End) @@ -106,7 +106,7 @@ namespace Avalonia.Markup.Parsers }); } - static (string ns, string name) ParseXamlIdentifier(ref CharacterReader r) + static (string? ns, string name) ParseXamlIdentifier(ref CharacterReader r) { var ident = r.ParseIdentifier(); if (ident.IsEmpty) @@ -147,7 +147,7 @@ namespace Avalonia.Markup.Parsers return true; } - private static (State, ISyntax) ParseAfterProperty(ref CharacterReader r) + private static (State, ISyntax?) ParseAfterProperty(ref CharacterReader r) { if (TryParseCasts(ref r, out var rv)) return rv; @@ -184,7 +184,7 @@ namespace Avalonia.Markup.Parsers public class PropertySyntax : ISyntax { - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public override bool Equals(object obj) => obj is PropertySyntax other @@ -193,9 +193,9 @@ namespace Avalonia.Markup.Parsers public class TypeQualifiedPropertySyntax : ISyntax { - public string Name { get; set; } - public string TypeName { get; set; } - public string TypeNamespace { get; set; } + public string Name { get; set; } = string.Empty; + public string TypeName { get; set; } = string.Empty; + public string? TypeNamespace { get; set; } public override bool Equals(object obj) => obj is TypeQualifiedPropertySyntax other @@ -212,8 +212,8 @@ namespace Avalonia.Markup.Parsers public class EnsureTypeSyntax : ISyntax { - public string TypeName { get; set; } - public string TypeNamespace { get; set; } + public string TypeName { get; set; } = string.Empty; + public string? TypeNamespace { get; set; } public override bool Equals(object obj) => obj is EnsureTypeSyntax other && other.TypeName == TypeName @@ -222,8 +222,8 @@ namespace Avalonia.Markup.Parsers public class CastTypeSyntax : ISyntax { - public string TypeName { get; set; } - public string TypeNamespace { get; set; } + public string TypeName { get; set; } = string.Empty; + public string? TypeNamespace { get; set; } public override bool Equals(object obj) => obj is CastTypeSyntax other && other.TypeName == TypeName diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index b25e9490cd..d821a04d93 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -39,7 +39,7 @@ namespace Avalonia.Markup.Parsers var selector = new List(); while (!r.End && state != State.End) { - ISyntax syntax = null; + ISyntax? syntax = null; switch (state) { case State.Start: @@ -110,7 +110,7 @@ namespace Avalonia.Markup.Parsers return State.TypeName; } - private static (State, ISyntax) ParseMiddle(ref CharacterReader r, char? end) + private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end) { if (r.TakeIf(':')) { @@ -190,7 +190,7 @@ namespace Avalonia.Markup.Parsers } } - private static (State, ISyntax) ParseTraversal(ref CharacterReader r) + private static (State, ISyntax?) ParseTraversal(ref CharacterReader r) { r.SkipWhitespace(); if (r.TakeIf('>')) @@ -325,7 +325,7 @@ namespace Avalonia.Markup.Parsers public class OfTypeSyntax : ISyntax, ITypeSyntax { - public string TypeName { get; set; } + public string TypeName { get; set; } = string.Empty; public string Xmlns { get; set; } = string.Empty; @@ -338,7 +338,7 @@ namespace Avalonia.Markup.Parsers public class IsSyntax : ISyntax, ITypeSyntax { - public string TypeName { get; set; } + public string TypeName { get; set; } = string.Empty; public string Xmlns { get; set; } = string.Empty; @@ -351,7 +351,7 @@ namespace Avalonia.Markup.Parsers public class ClassSyntax : ISyntax { - public string Class { get; set; } + public string Class { get; set; } = string.Empty; public override bool Equals(object obj) { @@ -361,7 +361,7 @@ namespace Avalonia.Markup.Parsers public class NameSyntax : ISyntax { - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public override bool Equals(object obj) { @@ -371,9 +371,9 @@ namespace Avalonia.Markup.Parsers public class PropertySyntax : ISyntax { - public string Property { get; set; } + public string Property { get; set; } = string.Empty; - public string Value { get; set; } + public string Value { get; set; } = string.Empty; public override bool Equals(object obj) { @@ -409,7 +409,7 @@ namespace Avalonia.Markup.Parsers public class NotSyntax : ISyntax { - public IEnumerable Argument { get; set; } + public IEnumerable Argument { get; set; } = Enumerable.Empty(); public override bool Equals(object obj) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 04519bf2bb..92ba744ee1 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -31,13 +31,13 @@ namespace Avalonia.Markup.Parsers /// /// The string. /// The parsed selector. - public Selector Parse(string s) + public Selector? Parse(string s) { var syntax = SelectorGrammar.Parse(s); return Create(syntax); } - private Selector Create(IEnumerable syntax) + private Selector? Create(IEnumerable syntax) { var result = default(Selector); var results = default(List); @@ -110,7 +110,7 @@ namespace Avalonia.Markup.Parsers results = new List(); } - results.Add(result); + results.Add(result ?? throw new NotSupportedException("Invalid selector!")); result = null; break; default: From fe6f66e77d9ac0f363d9bdc8e741e8970f89b9dc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 28 Jan 2021 19:00:20 +0100 Subject: [PATCH 03/12] Try all font styles to find a fallback typeface --- src/Skia/Avalonia.Skia/SKTypefaceCollection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 71deb1235f..7c4ff4edc0 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs @@ -32,7 +32,7 @@ namespace Avalonia.Skia weight -= weight % 100; // make sure we start at a full weight - for (var i = (int)key.Style; i < 2; i++) + for (var i = 0; i < 2; i++) { // only try 2 font weights in each direction for (var j = 0; j < 200; j += 100) @@ -57,8 +57,8 @@ namespace Avalonia.Skia } } - //Nothing was found so we use the first typeface we can get. - return typefaces.Values.FirstOrDefault(); + //Nothing was found so we try to get a regular typeface. + return typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface) ? typeface : null; } } } From 4bebe777d907ca07a39a4eb181dc1018384c9356 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 28 Jan 2021 20:06:52 +0100 Subject: [PATCH 04/12] Fix mock font manager and add typeface collection tests --- .../Media/CustomFontManagerImpl.cs | 4 +-- .../Media/SKTypefaceCollectionCacheTests.cs | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index a0fe348166..cc4b727bd9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media continue; } - typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight); + typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight); return true; } @@ -83,7 +83,7 @@ namespace Avalonia.Skia.UnitTests.Media case "Noto Mono": { var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + skTypeface = typefaceCollection.Get(_defaultTypeface); break; } default: diff --git a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs b/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs index f9f924e782..68813f28ab 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs @@ -7,25 +7,33 @@ namespace Avalonia.Skia.UnitTests.Media public class SKTypefaceCollectionCacheTests { [Fact] - public void Should_Load_Typefaces_From_Invalid_Name() + public void Should_Get_Near_Matching_Typeface() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { var notoMono = new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); - var colorEmoji = - new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Twitter Color Emoji"); - var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono); - var typeface = new Typeface("ABC", FontStyle.Italic, FontWeight.Bold); - - Assert.Equal("Noto Mono", notoMonoCollection.Get(typeface).FamilyName); - - var notoColorEmojiCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(colorEmoji); + Assert.Equal("Noto Mono", + notoMonoCollection.Get(new Typeface(notoMono, weight: FontWeight.Bold)).FamilyName); + } + } + + [Fact] + public void Should_Get_Null_For_Invalid_FamilyName() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var notoMono = + new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); + + var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono); - Assert.Equal("Twitter Color Emoji", notoColorEmojiCollection.Get(typeface).FamilyName); + var typeface = notoMonoCollection.Get(new Typeface("ABC")); + + Assert.Null(typeface); } } } From ef146125d202203f5d9cd16157786bcb5164fc93 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 7 Feb 2021 10:13:31 +0300 Subject: [PATCH 05/12] Add redirects for Q/A and discussions in issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 7 ++++--- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 1 - 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6b910fc615..4e34d4b132 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve Avalonia title: '' labels: bug assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,8 +24,9 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. Windows, Mac, Linux (State distribution)] - - Version [e.g. 0.10.0-rc1 or 0.9.12] + +- OS: [e.g. Windows, Mac, Linux (State distribution)] +- Version [e.g. 0.10.0-rc1 or 0.9.12] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..b5f5bda6fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Questions, Discussions, Ideas + url: https://github.com/AvaloniaUI/Avalonia/discussions/new + about: Please ask and answer questions here. + - name: Avalonia Community Support on Gitter + url: https://gitter.im/AvaloniaUI/Avalonia + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491ef1..5f0a04cee3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: '' - --- **Is your feature request related to a problem? Please describe.** From aeaccd4535e5a0cbd2e45be89ce2441de47d06dc Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 7 Feb 2021 16:42:06 +0300 Subject: [PATCH 06/12] Resolve conversation --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b5f5bda6fe..687355d825 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - name: Questions, Discussions, Ideas url: https://github.com/AvaloniaUI/Avalonia/discussions/new From 75d59d28a6bce51aa6f584f3184ae922682d9dd5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:57:13 +0100 Subject: [PATCH 07/12] Remove Contract usages. --- src/Markup/Avalonia.Markup/Data/Binding.cs | 3 ++- src/Markup/Avalonia.Markup/Data/BindingBase.cs | 13 +++++++------ .../Markup/Parsers/ExpressionObserverBuilder.cs | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 554c27b85a..71d2050e9e 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -63,7 +63,8 @@ namespace Avalonia.Data protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation) { - Contract.Requires(target is not null); + _ = target ?? throw new ArgumentNullException(nameof(target)); + anchor = anchor ?? DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index b900a043d7..c25ef49167 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -87,7 +87,8 @@ namespace Avalonia.Data object? anchor = null, bool enableDataValidation = false) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); + anchor = anchor ?? DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; @@ -135,7 +136,7 @@ namespace Avalonia.Data bool targetIsDataContext, object? anchor) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); if (!(target is IDataContextProvider)) { @@ -166,7 +167,7 @@ namespace Avalonia.Data string elementName, ExpressionNode node) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null) throw new InvalidOperationException("Name scope is null or was already collected"); @@ -182,7 +183,7 @@ namespace Avalonia.Data RelativeSource relativeSource, ExpressionNode node) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); IObservable controlLocator; @@ -214,7 +215,7 @@ namespace Avalonia.Data object source, ExpressionNode node) { - Contract.Requires(source != null); + _ = source ?? throw new ArgumentNullException(nameof(source)); return new ExpressionObserver(source, node); } @@ -223,7 +224,7 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node) { - Contract.Requires(target is not null); + _ = target ?? throw new ArgumentNullException(nameof(target)); var result = new ExpressionObserver( () => target.GetValue(StyledElement.TemplatedParentProperty), diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index e615923ff0..ebdad881c3 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -48,7 +48,8 @@ namespace Avalonia.Markup.Parsers string? description = null, Func? typeResolver = null) { - Contract.Requires(rootObservable != null); + _ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable)); + return new ExpressionObserver( rootObservable, Parse(expression, enableDataValidation, typeResolver).Node, @@ -64,7 +65,7 @@ namespace Avalonia.Markup.Parsers string? description = null, Func? typeResolver = null) { - Contract.Requires(rootGetter != null); + _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); return new ExpressionObserver( rootGetter, From f3a9f509d26f46a56de262a13fe1018b8228fa33 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:10:35 +0100 Subject: [PATCH 08/12] Fix nullable issues in Binding.cs, --- src/Markup/Avalonia.Markup/Data/Binding.cs | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 71d2050e9e..50be598d7f 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -65,8 +65,7 @@ namespace Avalonia.Data { _ = target ?? throw new ArgumentNullException(nameof(target)); - anchor = anchor ?? DefaultAnchor?.Target; - + anchor ??= DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; INameScope? nameScope = null; @@ -74,11 +73,22 @@ namespace Avalonia.Data var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope); -#nullable disable + if (node is null) + { + throw new InvalidOperationException("Could not parse binding expression."); + } + + IStyledElement GetSource() + { + return target as IStyledElement ?? + anchor as IStyledElement ?? + throw new ArgumentException("Could not find binding source: either target or anchor must be an IStyledElement."); + } + if (ElementName != null) { return CreateElementObserver( - (target as IStyledElement) ?? (anchor as IStyledElement), + GetSource(), ElementName, node); } @@ -98,9 +108,7 @@ namespace Avalonia.Data } else { - return CreateSourceObserver( - (target as IStyledElement) ?? (anchor as IStyledElement), - node); + return CreateSourceObserver(GetSource(), node); } } else if (RelativeSource.Mode == RelativeSourceMode.DataContext) @@ -113,15 +121,11 @@ namespace Avalonia.Data } else if (RelativeSource.Mode == RelativeSourceMode.Self) { - return CreateSourceObserver( - (target as IStyledElement) ?? (anchor as IStyledElement), - node); + return CreateSourceObserver(GetSource(), node); } else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) { - return CreateTemplatedParentObserver( - (target as IStyledElement) ?? (anchor as IStyledElement), - node); + return CreateTemplatedParentObserver(GetSource(), node); } else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor) { @@ -130,16 +134,12 @@ namespace Avalonia.Data throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree."); } - return CreateFindAncestorObserver( - (target as IStyledElement) ?? (anchor as IStyledElement), - RelativeSource, - node); + return CreateFindAncestorObserver(GetSource(), RelativeSource, node); } else { throw new NotSupportedException(); } } -#nullable enable } } From f56e4e0d2c91011c37258a3f962aba8deda2d3b5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:19:33 +0100 Subject: [PATCH 09/12] Fix nullable annotation. --- src/Markup/Avalonia.Markup/Data/MultiBinding.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 1a33536fa3..17b033f238 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -105,7 +105,7 @@ namespace Avalonia.Data } } - private object ConvertValue(IList values, Type targetType, IMultiValueConverter? converter) + private object ConvertValue(IList values, Type targetType, IMultiValueConverter? converter) { for (var i = 0; i < values.Count; ++i) { @@ -116,7 +116,7 @@ namespace Avalonia.Data } var culture = CultureInfo.CurrentCulture; - values = new System.Collections.ObjectModel.ReadOnlyCollection(values); + values = new System.Collections.ObjectModel.ReadOnlyCollection(values); object converted; if (converter != null) { From 311dbfa0889b01f8cf014c483f999148ceb83271 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:46:59 +0100 Subject: [PATCH 10/12] Add #nullable enable on shared files. Some files in Avalonia.Markup are linked into other projects and their use of `?` was causing warnings in the other projects. Explicitly add `#nullable enable` in these cases for now. --- .../Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs | 2 ++ .../Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs | 2 ++ src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 4da6a7f3ae..439bc15243 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -6,6 +6,8 @@ using Avalonia.Utilities; using System; using System.Collections.Generic; +#nullable enable + namespace Avalonia.Markup.Parsers { internal enum SourceMode diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 8e0a40ccf4..9b67f9c7bc 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Data.Core; using Avalonia.Utilities; +#nullable enable + namespace Avalonia.Markup.Parsers { #if !BUILDTASK diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index d821a04d93..af307ca1ac 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -4,6 +4,8 @@ using System.Linq; using Avalonia.Data.Core; using Avalonia.Utilities; +#nullable enable + // Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the // only reason they have overridden Equals methods is for unit testing. #pragma warning disable 659 From 6b0f3a26eb99a2db42a719d58bbf7f12db0cabd2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:52:57 +0100 Subject: [PATCH 11/12] Fix nullability of Equals methods. --- .../Markup/Parsers/PropertyPathGrammar.cs | 10 +++++----- .../Markup/Parsers/SelectorGrammar.cs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 9b67f9c7bc..250eca1852 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -188,7 +188,7 @@ namespace Avalonia.Markup.Parsers { public string Name { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is PropertySyntax other && other.Name == Name; } @@ -199,7 +199,7 @@ namespace Avalonia.Markup.Parsers public string TypeName { get; set; } = string.Empty; public string? TypeNamespace { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is TypeQualifiedPropertySyntax other && other.Name == Name && other.TypeName == TypeName @@ -209,14 +209,14 @@ namespace Avalonia.Markup.Parsers public class ChildTraversalSyntax : ISyntax { public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax(); - public override bool Equals(object obj) => obj is ChildTraversalSyntax; + public override bool Equals(object? obj) => obj is ChildTraversalSyntax; } public class EnsureTypeSyntax : ISyntax { public string TypeName { get; set; } = string.Empty; public string? TypeNamespace { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is EnsureTypeSyntax other && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; @@ -226,7 +226,7 @@ namespace Avalonia.Markup.Parsers { public string TypeName { get; set; } = string.Empty; public string? TypeNamespace { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is CastTypeSyntax other && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index af307ca1ac..9d03341f92 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -331,7 +331,7 @@ namespace Avalonia.Markup.Parsers public string Xmlns { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as OfTypeSyntax; return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns; @@ -344,7 +344,7 @@ namespace Avalonia.Markup.Parsers public string Xmlns { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as IsSyntax; return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns; @@ -355,7 +355,7 @@ namespace Avalonia.Markup.Parsers { public string Class { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class; } @@ -365,7 +365,7 @@ namespace Avalonia.Markup.Parsers { public string Name { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is NameSyntax && ((NameSyntax)obj).Name == Name; } @@ -377,7 +377,7 @@ namespace Avalonia.Markup.Parsers public string Value { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is PropertySyntax && ((PropertySyntax)obj).Property == Property && @@ -387,7 +387,7 @@ namespace Avalonia.Markup.Parsers public class ChildSyntax : ISyntax { - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ChildSyntax; } @@ -395,7 +395,7 @@ namespace Avalonia.Markup.Parsers public class DescendantSyntax : ISyntax { - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is DescendantSyntax; } @@ -403,7 +403,7 @@ namespace Avalonia.Markup.Parsers public class TemplateSyntax : ISyntax { - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is TemplateSyntax; } @@ -413,7 +413,7 @@ namespace Avalonia.Markup.Parsers { public IEnumerable Argument { get; set; } = Enumerable.Empty(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument); } @@ -421,7 +421,7 @@ namespace Avalonia.Markup.Parsers public class CommaSyntax : ISyntax { - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is CommaSyntax or; } From 922f765af64964fc3af33871142e74f7da8b9ded Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:53:52 +0100 Subject: [PATCH 12/12] Promote nullable warnings to errors. --- src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index ce104b3609..7b9cd0212e 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -3,6 +3,7 @@ netstandard2.0 Avalonia Enable + CS8600;CS8602;CS8603