Open-XML-SDK icon indicating copy to clipboard operation
Open-XML-SDK copied to clipboard

Incorrect nullability of MainDocumentPart.Document and corresponding root elements of other OpenXmlParts

Open ThomasBarnekow opened this issue 4 years ago • 4 comments

Description

The Document property of the MainDocumentPart is defined as follows:

        /// <summary>
        /// Gets or sets the root element of this part.
        /// </summary>
        public DocumentFormat.OpenXml.Wordprocessing.Document Document
        {
            get
            {
                if (_rootElement is null)
                {
                    LoadDomTree<DocumentFormat.OpenXml.Wordprocessing.Document>();
                }

                return _rootElement!;
            }

            set
            {
                if (value is null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                SetDomTree(value);
            }
        }

Other subclasses of OpenXmlPart (e.g., WorkbookPart) define their respective root elements (e.g., Workbook) in the same way.

Firstly, this is inconsistent with the more generic RootElement property of OpenXmlPart, which correctly flags nullability:

        /// <summary>
        /// Gets the root element of the current part.
        /// Returns null when the current part is empty or is not an XML content type.
        /// </summary>
        public OpenXmlPartRootElement? RootElement
        {
            get
            {
                return PartRootElement;
            }
        }

Secondly, when nullability is enabled, the definition suggests that Document, for example, will not be null. However, the following unit test demonstrates that this is not the case:

    public class MainDocumentPartTests
    {
        [Fact]
        public void Document_NewMainDocumentPart_DocumentIsNull()
        {
            using var stream = new MemoryStream();
            using var wordDocument = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document);
            MainDocumentPart mainDocumentPart = wordDocument.AddMainDocumentPart();

            Assert.NotNull(mainDocumentPart);
            Assert.Null(mainDocumentPart.Document);

            // ReSharper disable HeuristicUnreachableCode
            Assert.Throws<NullReferenceException>(() => mainDocumentPart.Document.Body);
        }
    }

You will also see the comment telling ReSharper to disable the HeuristicUnreachableCode check after asserting that mainDocumentPart.Document is null. Owing to the incorrect nullability, ReSharper believes the assertion will throw and, thus, the next assertion will never be reached. This is not correct, of course.

Information

  • .NET Target: all
  • DocumentFormat.OpenXml Version: latest

Repro

See the unit test above.

Observed

The nullability of root elements is stated as non-nullable.

Expected

The nullability of root elements should be stated as nullable.

ThomasBarnekow avatar Jul 07 '21 13:07 ThomasBarnekow

Good catch. Happy to accept a PR on this!

twsouthwick avatar Jul 23 '21 00:07 twsouthwick

@twsouthwick, this is generated code. Not sure whether anybody outside of Microsoft can help in this case.

ThomasBarnekow avatar Jul 23 '21 09:07 ThomasBarnekow

@twsouthwick, are you addressing this? As I said above, this is generated code, so the code generator will have to be fixed.

ThomasBarnekow avatar Aug 22 '21 22:08 ThomasBarnekow

The change needed:

  • Annotate property with [DisallowNull]
  • Mark the property as nullable

We'll try to get that in for 2.14

twsouthwick avatar Sep 15 '21 18:09 twsouthwick