Skip to content

Conversation

@am11
Copy link
Member

@am11 am11 commented Jan 8, 2026

  • Consolidate instruction tokens into main grammar to establish proper lexer precedence over DOTTEDNAME and ID; reorder methodDecl alternatives to match instructions first; add custom attribute handling in method bodies

  • Implement visitor stubs for debug/symbol directives: VisitLanguageDecl, VisitEsHead, VisitExportHead, VisitExtSourceSpec, VisitFieldInit

  • Add typedef resolution in ResolveTypeDef for type aliases

  • Implement label validation: track declared vs referenced labels, report errors for undefined labels at method end

  • Allow recoverable errors in method bodies to still emit assembly

  • Add tests for label validation, typedef resolution, and error handling

- Consolidate instruction tokens into main grammar to establish proper lexer
  precedence over DOTTEDNAME and ID; reorder methodDecl alternatives to
  match instructions first; add custom attribute handling in method bodies

- Implement visitor stubs for debug/symbol directives: VisitLanguageDecl,
  VisitEsHead, VisitExportHead, VisitExtSourceSpec, VisitFieldInit

- Add typedef resolution in ResolveTypeDef for type aliases

- Implement label validation: track declared vs referenced labels, report
  errors for undefined labels at method end

- Allow recoverable errors in method bodies to still emit assembly

- Add tests for label validation, typedef resolution, and error handling
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 8, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 8, 2026
@am11 am11 added area-ILTools-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 8, 2026
@am11 am11 requested a review from jkoritzinsky January 8, 2026 08:07
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT
See info in area-owners.md if you want to be subscribed.

}
return MetadataTokens.EntityHandle(tokenType, 0);
// If all lists are empty, return a nil handle
return default(EntityHandle);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return a handle for the right table at least (what we were doing before this change). That way we won't have a possible bug for callers that don't explicitly handle the Nil handle case.

static FieldDefinitionHandle GetFieldHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
{
var handle = GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.Field);
return handle.IsNil ? default : (FieldDefinitionHandle)handle;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we take the suggestion in GetHandleForList, we can convert all of these returns to just be a cast to the right handle type.

Comment on lines +125 to +137
INSTR_NONE: ('nop'|'break'|'ldarg.0'|'ldarg.1'|'ldarg.2'|'ldarg.3'|'ldloc.0'|'ldloc.1'|'ldloc.2'|'ldloc.3'|'stloc.0'|'stloc.1'|'stloc.2'|'stloc.3'|'ldnull'|'ldc.i4.m1'|'ldc.i4.0'|'ldc.i4.1'|'ldc.i4.2'|'ldc.i4.3'|'ldc.i4.4'|'ldc.i4.5'|'ldc.i4.6'|'ldc.i4.7'|'ldc.i4.8'|'dup'|'pop'|'ret'|'ldind.i1'|'ldind.u1'|'ldind.i2'|'ldind.u2'|'ldind.i4'|'ldind.u4'|'ldind.i8'|'ldind.i'|'ldind.r4'|'ldind.r8'|'ldind.ref'|'stind.ref'|'stind.i1'|'stind.i2'|'stind.i4'|'stind.i8'|'stind.r4'|'stind.r8'|'add'|'sub'|'mul'|'div'|'div.un'|'rem'|'rem.un'|'and'|'or'|'xor'|'shl'|'shr'|'shr.un'|'neg'|'not'|'conv.i1'|'conv.i2'|'conv.i4'|'conv.i8'|'conv.r4'|'conv.r8'|'conv.u4'|'conv.u8'|'conv.r.un'|'throw'|'conv.ovf.i1.un'|'conv.ovf.i2.un'|'conv.ovf.i4.un'|'conv.ovf.i8.un'|'conv.ovf.u1.un'|'conv.ovf.u2.un'|'conv.ovf.u4.un'|'conv.ovf.u8.un'|'conv.ovf.i.un'|'conv.ovf.u.un'|'ldlen'|'ldelem.i1'|'ldelem.u1'|'ldelem.i2'|'ldelem.u2'|'ldelem.i4'|'ldelem.u4'|'ldelem.i8'|'ldelem.i'|'ldelem.r4'|'ldelem.r8'|'ldelem.ref'|'stelem.i'|'stelem.i1'|'stelem.i2'|'stelem.i4'|'stelem.i8'|'stelem.r4'|'stelem.r8'|'stelem.ref'|'conv.ovf.i1'|'conv.ovf.u1'|'conv.ovf.i2'|'conv.ovf.u2'|'conv.ovf.i4'|'conv.ovf.u4'|'conv.ovf.i8'|'conv.ovf.u8'|'ckfinite'|'conv.u2'|'conv.u1'|'conv.i'|'conv.ovf.i'|'conv.ovf.u'|'add.ovf'|'add.ovf.un'|'mul.ovf'|'mul.ovf.un'|'sub.ovf'|'sub.ovf.un'|'endfinally'|'stind.i'|'conv.u'|'prefix7'|'prefix6'|'prefix5'|'prefix4'|'prefix3'|'prefix2'|'prefix1'|'prefixref'|'arglist'|'ceq'|'cgt'|'cgt.un'|'clt'|'clt.un'|'localloc'|'endfilter'|'volatile.'|'tail.'|'cpblk'|'initblk'|'rethrow'|'refanytype'|'readonly.'|'illegal'|'endmac');
INSTR_VAR: ('ldarg.s'|'ldarga.s'|'starg.s'|'ldloc.s'|'ldloca.s'|'stloc.s'|'ldarg'|'ldarga'|'starg'|'ldloc'|'ldloca'|'stloc');
INSTR_I: ('ldc.i4.s'|'ldc.i4'|'unaligned.'|'no.');
INSTR_I8: ('ldc.i8');
INSTR_R: ('ldc.r4'|'ldc.r8');
INSTR_METHOD: ('jmp'|'call'|'callvirt'|'newobj'|'ldftn'|'ldvirtftn');
INSTR_SIG: ('calli');
INSTR_BRTARGET: ('br.s'|'brfalse.s'|'brtrue.s'|'beq.s'|'bge.s'|'bgt.s'|'ble.s'|'blt.s'|'bne.un.s'|'bge.un.s'|'bgt.un.s'|'ble.un.s'|'blt.un.s'|'br'|'brfalse'|'brtrue'|'beq'|'bge'|'bgt'|'ble'|'blt'|'bne.un'|'bge.un'|'bgt.un'|'ble.un'|'blt.un'|'leave'|'leave.s');
INSTR_SWITCH: ('switch');
INSTR_TYPE: ('cpobj'|'ldobj'|'castclass'|'isinst'|'unbox'|'stobj'|'box'|'newarr'|'ldelema'|'ldelem'|'stelem'|'unbox.any'|'refanyval'|'mkrefany'|'initobj'|'constrained.'|'sizeof');
INSTR_STRING: ('ldstr');
INSTR_FIELD: ('ldfld'|'ldflda'|'stfld'|'ldsfld'|'ldsflda'|'stsfld');
INSTR_TOK: ('ldtoken');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we format these like the rest of the rules in this file?

Comment on lines +907 to +908
// If we got here without exception, the code path was handled
Assert.True(diagnostics.Length >= 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we match the specific error diagnostics instead of asserting "we have diagnostics"?

""";

var diagnostics = CompileAndGetDiagnostics(source, new Options());
// BaseOutsideClass is hard to trigger with valid IL - just verify compilation works
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For cases where we can't write a test that actually has the IL that triggers it, I'd rather we don't have the test than have a test that doesn't actually validate the diagnostic.

Comment on lines +161 to +169
private static bool IsRecoverableError(string diagnosticId)
{
// Method body diagnostics are recoverable - we emit the assembly but report the error
return diagnosticId is DiagnosticIds.ByteArrayTooShort
or DiagnosticIds.ArgumentNotFound
or DiagnosticIds.LocalNotFound
or DiagnosticIds.LabelNotFound;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think more of these errors might be recoverable, but I'm not 100% positive. Can we compare against ilasm to see what's recoverable there?

For example, I think GenericParameterIndexOutOfRange should be recoverable (the method signature would be invalid, but should still be emittable).

}

public GrammarResult VisitExtSourceSpec(CILParser.ExtSourceSpecContext context) => throw new NotImplementedException("TODO: Symbols");
public GrammarResult VisitExtSourceSpec(CILParser.ExtSourceSpecContext context) => GrammarResult.SentinelValue.Result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These symbols TODOs haven't been fixed.

}

public GrammarResult VisitFieldInit(CILParser.FieldInitContext context) => throw new NotImplementedException("TODO-SRM: Need support for an arbitrary byte blob as a constant value");
public GrammarResult VisitFieldInit(CILParser.FieldInitContext context) => GrammarResult.SentinelValue.Result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still a TODO

}

public GrammarResult VisitExportHead(CILParser.ExportHeadContext context) => throw new NotImplementedException("Obsolete syntax");
public GrammarResult VisitExportHead(CILParser.ExportHeadContext context) => GrammarResult.SentinelValue.Result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this a no-op isn't the right thing. We should add support for this in VisitClassDecl if we want to support it, or (eventually) remove it from the grammar if we don't want to support it any more.


public GrammarResult VisitErrorNode(IErrorNode node) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
public GrammarResult VisitEsHead(CILParser.EsHeadContext context) => throw new NotImplementedException("TODO: Symbols");
public GrammarResult VisitEsHead(CILParser.EsHeadContext context) => GrammarResult.SentinelValue.Result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another TODO that hasn't been implemented yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-ILTools-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants