diff --git a/docs/episode-26.md b/docs/episode-26.md new file mode 100644 index 00000000..4752ebd7 --- /dev/null +++ b/docs/episode-26.md @@ -0,0 +1,64 @@ +# Episode 26 + +[Video](https://www.youtube.com/watch?v=Y2Gn6qr_twA&list=PLRAdsfhKI4OWNOSfS7EUu5GRAVmze1t2y&index=26) | +[Pull Request](https://github.com/terrajobst/minsk/pull/148) | +[Previous](episode-26.md) | +[Next](episode-27.md) + +## Completed items + +* Enabled nullable in Minsk.Tests & Minsk.Generators +* Honored nullable annotations in source generator +* Filed and fixed various TODOs + +## Interesting aspects + +### Leveraging nullable annotations inside the source generator + +We're now honoring nullable annotations when generating the +`SyntaxNode.GetChildren()` methods. + +For example, consider `ReturnStatementSyntax`. The `Expression` property is +marked as being nullable (because `return` can be used without an expression). + +```C# +partial class ReturnStatementSyntax : StatementSyntax +{ + public SyntaxToken ReturnKeyword { get; } + public ExpressionSyntax? Expression { get; } +} +``` + +Our generator now uses the null annotation for the `Expression` property and +emits a `null` check: + +```C# +partial class ReturnStatementSyntax +{ + public override IEnumerable GetChildren() + { + yield return ReturnKeyword; + if (Expression != null) + yield return Expression; + } +} +``` + +This is simply done by [using Roslyn's `NullableAnnotation` +property][null-annotations]: + +```C# +var canBeNull = property.NullableAnnotation == NullableAnnotation.Annotated; +if (canBeNull) +{ + writer.WriteLine($"if ({property.Name} != null)"); + writer.Indent++; +} + +writer.WriteLine($"yield return {property.Name};"); + +if (canBeNull) + writer.Indent--; +``` + +[null-annotations]: https://github.com/terrajobst/minsk/blob/877fefa36e184da125fd62942b5797328df79896/src/Minsk.Generators/SyntaxNodeGetChildrenGenerator.cs#L59-L69 diff --git a/src/Minsk.Generators/Minsk.Generators.csproj b/src/Minsk.Generators/Minsk.Generators.csproj index 32f15233..3d6bd53e 100644 --- a/src/Minsk.Generators/Minsk.Generators.csproj +++ b/src/Minsk.Generators/Minsk.Generators.csproj @@ -4,8 +4,6 @@ false false netstandard2.0 - - Disable diff --git a/src/Minsk.Generators/SyntaxNodeGetChildrenGenerator.cs b/src/Minsk.Generators/SyntaxNodeGetChildrenGenerator.cs index e27df3e4..818665bc 100644 --- a/src/Minsk.Generators/SyntaxNodeGetChildrenGenerator.cs +++ b/src/Minsk.Generators/SyntaxNodeGetChildrenGenerator.cs @@ -29,6 +29,9 @@ public void Execute(SourceGeneratorContext context) var separatedSyntaxListType = compilation.GetTypeByMetadataName("Minsk.CodeAnalysis.Syntax.SeparatedSyntaxList`1"); var syntaxNodeType = compilation.GetTypeByMetadataName("Minsk.CodeAnalysis.Syntax.SyntaxNode"); + if (immutableArrayType == null || separatedSyntaxListType == null || syntaxNodeType == null) + return; + var types = GetAllTypes(compilation.Assembly); var syntaxNodeTypes = types.Where(t => !t.IsAbstract && IsPartial(t) && IsDerivedFrom(t, syntaxNodeType)); @@ -53,7 +56,17 @@ public void Execute(SourceGeneratorContext context) { if (IsDerivedFrom(propertyType, syntaxNodeType)) { + var canBeNull = property.NullableAnnotation == NullableAnnotation.Annotated; + if (canBeNull) + { + indentedTextWriter.WriteLine($"if ({property.Name} != null)"); + indentedTextWriter.Indent++; + } + indentedTextWriter.WriteLine($"yield return {property.Name};"); + + if (canBeNull) + indentedTextWriter.Indent--; } else if (propertyType.TypeArguments.Length == 1 && IsDerivedFrom(propertyType.TypeArguments[0], syntaxNodeType) && @@ -61,7 +74,7 @@ public void Execute(SourceGeneratorContext context) { indentedTextWriter.WriteLine($"foreach (var child in {property.Name})"); indentedTextWriter.WriteLine($"{indentString}yield return child;"); - + } else if (SymbolEqualityComparer.Default.Equals(propertyType.OriginalDefinition, separatedSyntaxListType) && IsDerivedFrom(propertyType.TypeArguments[0], syntaxNodeType)) @@ -127,12 +140,14 @@ private void GetAllTypes(List result, INamespaceOrTypeSymbol s private bool IsDerivedFrom(ITypeSymbol type, INamedTypeSymbol baseType) { - while (type != null) + var current = type; + + while (current != null) { - if (SymbolEqualityComparer.Default.Equals(type, baseType)) + if (SymbolEqualityComparer.Default.Equals(current, baseType)) return true; - type = type.BaseType; + current = current.BaseType; } return false; diff --git a/src/Minsk.Tests/CodeAnalysis/AnnotatedText.cs b/src/Minsk.Tests/CodeAnalysis/AnnotatedText.cs index 45239ac5..81303a6f 100644 --- a/src/Minsk.Tests/CodeAnalysis/AnnotatedText.cs +++ b/src/Minsk.Tests/CodeAnalysis/AnnotatedText.cs @@ -69,7 +69,7 @@ public static string[] UnindentLines(string text) using (var reader = new StringReader(text)) { - string line; + string? line; while ((line = reader.ReadLine()) != null) lines.Add(line); } diff --git a/src/Minsk.Tests/CodeAnalysis/Syntax/LexerTests.cs b/src/Minsk.Tests/CodeAnalysis/Syntax/LexerTests.cs index bf018505..152f36d9 100644 --- a/src/Minsk.Tests/CodeAnalysis/Syntax/LexerTests.cs +++ b/src/Minsk.Tests/CodeAnalysis/Syntax/LexerTests.cs @@ -144,9 +144,9 @@ public static IEnumerable GetTokenPairsWithSeparatorData() { var fixedTokens = Enum.GetValues(typeof(SyntaxKind)) .Cast() - .Select(k => (kind: k, text: SyntaxFacts.GetText(k))) - .Where(t => t.text != null); - + .Select(k => (k, text: SyntaxFacts.GetText(k))) + .Where(t => t.text != null) + .Cast<(SyntaxKind, string)>(); var dynamicTokens = new[] { diff --git a/src/Minsk.Tests/CodeAnalysis/Syntax/ParserTests.cs b/src/Minsk.Tests/CodeAnalysis/Syntax/ParserTests.cs index 4d5c1937..77404286 100644 --- a/src/Minsk.Tests/CodeAnalysis/Syntax/ParserTests.cs +++ b/src/Minsk.Tests/CodeAnalysis/Syntax/ParserTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using Minsk.CodeAnalysis.Syntax; using Xunit; @@ -17,6 +18,9 @@ public void Parser_BinaryExpression_HonorsPrecedences(SyntaxKind op1, SyntaxKind var text = $"a {op1Text} b {op2Text} c"; var expression = ParseExpression(text); + Debug.Assert(op1Text != null); + Debug.Assert(op2Text != null); + if (op1Precedence >= op2Precedence) { // op2 @@ -74,6 +78,9 @@ public void Parser_UnaryExpression_HonorsPrecedences(SyntaxKind unaryKind, Synta var text = $"{unaryText} a {binaryText} b"; var expression = ParseExpression(text); + Debug.Assert(unaryText != null); + Debug.Assert(binaryText != null); + if (unaryPrecedence >= binaryPrecedence) { // binary diff --git a/src/Minsk.Tests/Minsk.Tests.csproj b/src/Minsk.Tests/Minsk.Tests.csproj index 0c0b1cd1..f5458e9a 100644 --- a/src/Minsk.Tests/Minsk.Tests.csproj +++ b/src/Minsk.Tests/Minsk.Tests.csproj @@ -4,8 +4,6 @@ false false netcoreapp3.1 - - Disable diff --git a/src/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs b/src/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs index 651d7b8a..4349a0cb 100644 --- a/src/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs +++ b/src/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs @@ -10,7 +10,7 @@ public BoundBinaryExpression(BoundExpression left, BoundBinaryOperator op, Bound Left = left; Op = op; Right = right; - ConstantValue = ConstantFolding.ComputeConstant(left, op, right); + ConstantValue = ConstantFolding.Fold(left, op, right); } public override BoundNodeKind Kind => BoundNodeKind.BinaryExpression; diff --git a/src/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs b/src/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs index f63ef26d..6defd19a 100644 --- a/src/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs +++ b/src/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs @@ -9,7 +9,7 @@ public BoundUnaryExpression(BoundUnaryOperator op, BoundExpression operand) { Op = op; Operand = operand; - ConstantValue = ConstantFolding.ComputeConstant(op, operand); + ConstantValue = ConstantFolding.Fold(op, operand); } public override BoundNodeKind Kind => BoundNodeKind.UnaryExpression; diff --git a/src/Minsk/CodeAnalysis/Binding/ConstantFolding.cs b/src/Minsk/CodeAnalysis/Binding/ConstantFolding.cs index a99097e1..74975de9 100644 --- a/src/Minsk/CodeAnalysis/Binding/ConstantFolding.cs +++ b/src/Minsk/CodeAnalysis/Binding/ConstantFolding.cs @@ -5,7 +5,7 @@ namespace Minsk.CodeAnalysis.Binding { internal static class ConstantFolding { - public static BoundConstant? ComputeConstant(BoundUnaryOperator op, BoundExpression operand) + public static BoundConstant? Fold(BoundUnaryOperator op, BoundExpression operand) { if (operand.ConstantValue != null) { @@ -27,7 +27,7 @@ internal static class ConstantFolding return null; } - public static BoundConstant? ComputeConstant(BoundExpression left, BoundBinaryOperator op, BoundExpression right) + public static BoundConstant? Fold(BoundExpression left, BoundBinaryOperator op, BoundExpression right) { var leftConstant = left.ConstantValue; var rightConstant = right.ConstantValue; diff --git a/src/Minsk/CodeAnalysis/Compilation.cs b/src/Minsk/CodeAnalysis/Compilation.cs index 846b56c7..70df6743 100644 --- a/src/Minsk/CodeAnalysis/Compilation.cs +++ b/src/Minsk/CodeAnalysis/Compilation.cs @@ -128,6 +128,7 @@ public void EmitTree(FunctionSymbol symbol, TextWriter writer) body.WriteTo(writer); } + // TODO: References should be part of the compilation, not arguments for Emit public ImmutableArray Emit(string moduleName, string[] references, string outputPath) { var parseDiagnostics = SyntaxTrees.SelectMany(st => st.Diagnostics); diff --git a/src/Minsk/CodeAnalysis/Diagnostic.cs b/src/Minsk/CodeAnalysis/Diagnostic.cs index e02370b2..5204d335 100644 --- a/src/Minsk/CodeAnalysis/Diagnostic.cs +++ b/src/Minsk/CodeAnalysis/Diagnostic.cs @@ -4,29 +4,29 @@ namespace Minsk.CodeAnalysis { public sealed class Diagnostic { - private Diagnostic(TextLocation location, string message, bool isError) + private Diagnostic(bool isError, TextLocation location, string message) { + IsError = isError; Location = location; Message = message; - IsError = isError; IsWarning = !IsError; } + public bool IsError { get; } public TextLocation Location { get; } public string Message { get; } - public bool IsError { get; } public bool IsWarning { get; } public override string ToString() => Message; public static Diagnostic Error(TextLocation location, string message) { - return new Diagnostic(location, message, isError: true); + return new Diagnostic(isError: true, location, message); } public static Diagnostic Warning(TextLocation location, string message) { - return new Diagnostic(location, message, isError: false); + return new Diagnostic(isError: false, location, message); } } } \ No newline at end of file diff --git a/src/Minsk/CodeAnalysis/DiagnosticBag.cs b/src/Minsk/CodeAnalysis/DiagnosticBag.cs index 71ce3663..83c9c2aa 100644 --- a/src/Minsk/CodeAnalysis/DiagnosticBag.cs +++ b/src/Minsk/CodeAnalysis/DiagnosticBag.cs @@ -22,7 +22,7 @@ public void AddRange(IEnumerable diagnostics) _diagnostics.AddRange(diagnostics); } - private void Report(TextLocation location, string message) + private void ReportError(TextLocation location, string message) { var diagnostic = Diagnostic.Error(location, message); _diagnostics.Add(diagnostic); @@ -37,175 +37,175 @@ private void ReportWarning(TextLocation location, string message) public void ReportInvalidNumber(TextLocation location, string text, TypeSymbol type) { var message = $"The number {text} isn't valid {type}."; - Report(location, message); + ReportError(location, message); } public void ReportBadCharacter(TextLocation location, char character) { var message = $"Bad character input: '{character}'."; - Report(location, message); + ReportError(location, message); } public void ReportUnterminatedString(TextLocation location) { var message = "Unterminated string literal."; - Report(location, message); + ReportError(location, message); } public void ReportUnterminatedMultiLineComment(TextLocation location) { var message = "Unterminated multi-line comment."; - Report(location, message); + ReportError(location, message); } public void ReportUnexpectedToken(TextLocation location, SyntaxKind actualKind, SyntaxKind expectedKind) { var message = $"Unexpected token <{actualKind}>, expected <{expectedKind}>."; - Report(location, message); + ReportError(location, message); } public void ReportUndefinedUnaryOperator(TextLocation location, string operatorText, TypeSymbol operandType) { var message = $"Unary operator '{operatorText}' is not defined for type '{operandType}'."; - Report(location, message); + ReportError(location, message); } public void ReportUndefinedBinaryOperator(TextLocation location, string operatorText, TypeSymbol leftType, TypeSymbol rightType) { var message = $"Binary operator '{operatorText}' is not defined for types '{leftType}' and '{rightType}'."; - Report(location, message); + ReportError(location, message); } public void ReportParameterAlreadyDeclared(TextLocation location, string parameterName) { var message = $"A parameter with the name '{parameterName}' already exists."; - Report(location, message); + ReportError(location, message); } public void ReportUndefinedVariable(TextLocation location, string name) { var message = $"Variable '{name}' doesn't exist."; - Report(location, message); + ReportError(location, message); } public void ReportNotAVariable(TextLocation location, string name) { var message = $"'{name}' is not a variable."; - Report(location, message); + ReportError(location, message); } public void ReportUndefinedType(TextLocation location, string name) { var message = $"Type '{name}' doesn't exist."; - Report(location, message); + ReportError(location, message); } public void ReportCannotConvert(TextLocation location, TypeSymbol fromType, TypeSymbol toType) { var message = $"Cannot convert type '{fromType}' to '{toType}'."; - Report(location, message); + ReportError(location, message); } public void ReportCannotConvertImplicitly(TextLocation location, TypeSymbol fromType, TypeSymbol toType) { var message = $"Cannot convert type '{fromType}' to '{toType}'. An explicit conversion exists (are you missing a cast?)"; - Report(location, message); + ReportError(location, message); } public void ReportSymbolAlreadyDeclared(TextLocation location, string name) { var message = $"'{name}' is already declared."; - Report(location, message); + ReportError(location, message); } public void ReportCannotAssign(TextLocation location, string name) { var message = $"Variable '{name}' is read-only and cannot be assigned to."; - Report(location, message); + ReportError(location, message); } public void ReportUndefinedFunction(TextLocation location, string name) { var message = $"Function '{name}' doesn't exist."; - Report(location, message); + ReportError(location, message); } public void ReportNotAFunction(TextLocation location, string name) { var message = $"'{name}' is not a function."; - Report(location, message); + ReportError(location, message); } public void ReportWrongArgumentCount(TextLocation location, string name, int expectedCount, int actualCount) { var message = $"Function '{name}' requires {expectedCount} arguments but was given {actualCount}."; - Report(location, message); + ReportError(location, message); } public void ReportExpressionMustHaveValue(TextLocation location) { var message = "Expression must have a value."; - Report(location, message); + ReportError(location, message); } public void ReportInvalidBreakOrContinue(TextLocation location, string text) { var message = $"The keyword '{text}' can only be used inside of loops."; - Report(location, message); + ReportError(location, message); } public void ReportAllPathsMustReturn(TextLocation location) { var message = "Not all code paths return a value."; - Report(location, message); + ReportError(location, message); } public void ReportInvalidReturnExpression(TextLocation location, string functionName) { var message = $"Since the function '{functionName}' does not return a value the 'return' keyword cannot be followed by an expression."; - Report(location, message); + ReportError(location, message); } public void ReportInvalidReturnWithValueInGlobalStatements(TextLocation location) { var message = "The 'return' keyword cannot be followed by an expression in global statements."; - Report(location, message); + ReportError(location, message); } public void ReportMissingReturnExpression(TextLocation location, TypeSymbol returnType) { var message = $"An expression of type '{returnType}' is expected."; - Report(location, message); + ReportError(location, message); } public void ReportInvalidExpressionStatement(TextLocation location) { var message = $"Only assignment and call expressions can be used as a statement."; - Report(location, message); + ReportError(location, message); } public void ReportOnlyOneFileCanHaveGlobalStatements(TextLocation location) { var message = $"At most one file can have global statements."; - Report(location, message); + ReportError(location, message); } public void ReportMainMustHaveCorrectSignature(TextLocation location) { var message = $"main must not take arguments and not return anything."; - Report(location, message); + ReportError(location, message); } public void ReportCannotMixMainAndGlobalStatements(TextLocation location) { var message = $"Cannot declare main function when global statements are used."; - Report(location, message); + ReportError(location, message); } public void ReportInvalidReference(string path) { var message = $"The reference is not a valid .NET assembly: '{path}'"; - Report(default, message); + ReportError(default, message); } public void ReportRequiredTypeNotFound(string? minskName, string metadataName) @@ -213,7 +213,7 @@ public void ReportRequiredTypeNotFound(string? minskName, string metadataName) var message = minskName == null ? $"The required type '{metadataName}' cannot be resolved among the given references." : $"The required type '{minskName}' ('{metadataName}') cannot be resolved among the given references."; - Report(default, message); + ReportError(default, message); } public void ReportRequiredTypeAmbiguous(string? minskName, string metadataName, TypeDefinition[] foundTypes) @@ -223,14 +223,14 @@ public void ReportRequiredTypeAmbiguous(string? minskName, string metadataName, var message = minskName == null ? $"The required type '{metadataName}' was found in multiple references: {assemblyNameList}." : $"The required type '{minskName}' ('{metadataName}') was found in multiple references: {assemblyNameList}."; - Report(default, message); + ReportError(default, message); } public void ReportRequiredMethodNotFound(string typeName, string methodName, string[] parameterTypeNames) { var parameterTypeNameList = string.Join(", ", parameterTypeNames); var message = $"The required method '{typeName}.{methodName}({parameterTypeNameList})' cannot be resolved among the given references."; - Report(default, message); + ReportError(default, message); } public void ReportUnreachableCode(TextLocation location) diff --git a/src/Minsk/CodeAnalysis/Emit/Emitter.cs b/src/Minsk/CodeAnalysis/Emit/Emitter.cs index 3c9ea481..9f4de903 100644 --- a/src/Minsk/CodeAnalysis/Emit/Emitter.cs +++ b/src/Minsk/CodeAnalysis/Emit/Emitter.cs @@ -40,6 +40,7 @@ internal sealed class Emitter private TypeDefinition _typeDefinition; private FieldDefinition? _randomFieldDefinition; + // TOOD: This constructor does too much. Resolution should be factored out. private Emitter(string moduleName, string[] references) { var assemblies = new List(); @@ -489,12 +490,12 @@ private void EmitBinaryExpression(ILProcessor ilProcessor, BoundBinaryExpression case BoundBinaryOperatorKind.Division: ilProcessor.Emit(OpCodes.Div); break; - // TODO: Implement short-circuit evaluation + // TODO: Implement short-circuit evaluation #111 case BoundBinaryOperatorKind.LogicalAnd: case BoundBinaryOperatorKind.BitwiseAnd: ilProcessor.Emit(OpCodes.And); break; - // TODO: Implement short-circuit evaluation + // TODO: Implement short-circuit evaluation #111 case BoundBinaryOperatorKind.LogicalOr: case BoundBinaryOperatorKind.BitwiseOr: ilProcessor.Emit(OpCodes.Or); diff --git a/src/Minsk/CodeAnalysis/EvaluationResult.cs b/src/Minsk/CodeAnalysis/EvaluationResult.cs index 263899ec..4edb62a8 100644 --- a/src/Minsk/CodeAnalysis/EvaluationResult.cs +++ b/src/Minsk/CodeAnalysis/EvaluationResult.cs @@ -13,6 +13,8 @@ public EvaluationResult(ImmutableArray diagnostics, object? value) WarningDiagnostics = Diagnostics.Where(d => d.IsWarning).ToImmutableArray(); } + // TODO: I think we should not have separate collections but instead + // have an extension method over ImmutableArray public ImmutableArray Diagnostics { get; } public ImmutableArray ErrorDiagnostics { get; } public ImmutableArray WarningDiagnostics { get; } diff --git a/src/Minsk/CodeAnalysis/Evaluator.cs b/src/Minsk/CodeAnalysis/Evaluator.cs index 885029b5..eeeb7537 100644 --- a/src/Minsk/CodeAnalysis/Evaluator.cs +++ b/src/Minsk/CodeAnalysis/Evaluator.cs @@ -6,6 +6,7 @@ namespace Minsk.CodeAnalysis { + // TODO: Get rid of evaluator in favor of Emitter (see #113) internal sealed class Evaluator { private readonly BoundProgram _program; diff --git a/src/Minsk/CodeAnalysis/Lowering/Lowerer.cs b/src/Minsk/CodeAnalysis/Lowering/Lowerer.cs index bd463c0f..226e62a9 100644 --- a/src/Minsk/CodeAnalysis/Lowering/Lowerer.cs +++ b/src/Minsk/CodeAnalysis/Lowering/Lowerer.cs @@ -8,6 +8,7 @@ namespace Minsk.CodeAnalysis.Lowering { + // TODO: Consider creating a BoundNodeFactory to construct nodes to make lowering easier to read. internal sealed class Lowerer : BoundTreeRewriter { private int _labelCount; @@ -63,10 +64,6 @@ private static BoundBlockStatement Flatten(FunctionSymbol function, BoundStateme private static bool CanFallThrough(BoundStatement boundStatement) { - // TODO: We don't rewrite conditional gotos where the condition is - // always true. We shouldn't handle this here, because we - // should really rewrite those to unconditional gotos in the - // first place. return boundStatement.Kind != BoundNodeKind.ReturnStatement && boundStatement.Kind != BoundNodeKind.GotoStatement; } diff --git a/src/Minsk/CodeAnalysis/Symbols/Symbol.cs b/src/Minsk/CodeAnalysis/Symbols/Symbol.cs index 6725a85f..f1ff00b6 100644 --- a/src/Minsk/CodeAnalysis/Symbols/Symbol.cs +++ b/src/Minsk/CodeAnalysis/Symbols/Symbol.cs @@ -2,6 +2,7 @@ namespace Minsk.CodeAnalysis.Symbols { + // TODO: Constructors should be internal public abstract class Symbol { private protected Symbol(string name) diff --git a/src/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs b/src/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs index 01faef98..60626683 100644 --- a/src/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs +++ b/src/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs @@ -6,6 +6,7 @@ namespace Minsk.CodeAnalysis.Syntax { + // TODO: All constructors should be internal public abstract class SyntaxNode { protected SyntaxNode(SyntaxTree syntaxTree) @@ -57,10 +58,6 @@ public void WriteTo(TextWriter writer) private static void PrettyPrint(TextWriter writer, SyntaxNode node, string indent = "", bool isLast = true) { - // HACK: node shoud never be null, but that's tracked by #141 - if (node == null) - return; - var isToConsole = writer == Console.Out; var token = node as SyntaxToken; diff --git a/src/msi/Authoring/Classifier.cs b/src/msi/Authoring/Classifier.cs index 0cb4e37f..ca0d45ec 100644 --- a/src/msi/Authoring/Classifier.cs +++ b/src/msi/Authoring/Classifier.cs @@ -17,8 +17,7 @@ public static ImmutableArray Classify(SyntaxTree syntaxTree, Tex private static void ClassifyNode(SyntaxNode node, TextSpan span, ImmutableArray.Builder result) { - // HACK: node should never be null, but that's tracked by #141 - if (node == null || !node.FullSpan.OverlapsWith(span)) + if (!node.FullSpan.OverlapsWith(span)) return; if (node is SyntaxToken token)