Fixed annotation parsing in AstBuilder

pulled some hair out on this one
This commit is contained in:
2025-07-23 07:29:16 -04:00
parent 1e15e90d32
commit 85fdcff103
8 changed files with 492 additions and 58 deletions

View File

@@ -297,11 +297,24 @@ namespace MarketAlly.IronJava.Core.AST.Builders
var annotations = new List<Annotation>();
foreach (var context in contexts)
{
// Check if this is directly an annotation context
if (context is Java9Parser.AnnotationContext annotationContext)
{
var annotation = BuildAnnotation(annotationContext);
if (annotation != null) annotations.Add(annotation);
}
// Check if this is a modifier that contains an annotation
else if (context is ParserRuleContext ruleContext && ruleContext.ChildCount > 0)
{
// The first child of a modifier rule that represents an annotation
// will be an AnnotationContext
var firstChild = ruleContext.GetChild(0);
if (firstChild is Java9Parser.AnnotationContext childAnnotation)
{
var annotation = BuildAnnotation(childAnnotation);
if (annotation != null) annotations.Add(annotation);
}
}
}
return annotations;
}
@@ -312,25 +325,37 @@ namespace MarketAlly.IronJava.Core.AST.Builders
TypeReference? type = null;
var arguments = new List<AnnotationArgument>();
// Annotations can be NormalAnnotation, MarkerAnnotation, or SingleElementAnnotation
if (context.normalAnnotation() != null)
{
var normalAnn = context.normalAnnotation();
type = BuildTypeReference(normalAnn.typeName());
if (type == null) return null;
// Don't return early - try to build the type from the name
if (type == null)
{
var typeName = normalAnn.typeName().GetText();
type = new ClassOrInterfaceType(GetSourceRange(normalAnn.typeName()), typeName, null, new List<TypeArgument>(), new List<Annotation>());
}
if (normalAnn.elementValuePairList() != null)
{
foreach (var pair in normalAnn.elementValuePairList().elementValuePair())
{
var name = pair.identifier().GetText();
var value = BuildExpression(pair.elementValue());
if (value != null)
var value = BuildElementValue(pair.elementValue());
// Debug - always add the argument even if value is null
// If value is null, create a string literal as fallback
if (value == null)
{
arguments.Add(new AnnotationValueArgument(
GetSourceRange(pair), name, value
));
var elementText = pair.elementValue().GetText();
value = new LiteralExpression(GetSourceRange(pair.elementValue()), elementText, LiteralKind.String);
}
arguments.Add(new AnnotationValueArgument(
GetSourceRange(pair), name, value
));
}
}
}
@@ -338,30 +363,89 @@ namespace MarketAlly.IronJava.Core.AST.Builders
{
var markerAnn = context.markerAnnotation();
type = BuildTypeReference(markerAnn.typeName());
if (type == null) return null;
if (type == null)
{
var typeName = markerAnn.typeName().GetText();
type = new ClassOrInterfaceType(GetSourceRange(markerAnn.typeName()), typeName, null, new List<TypeArgument>(), new List<Annotation>());
}
// Marker annotations have no arguments
}
else if (context.singleElementAnnotation() != null)
{
var singleAnn = context.singleElementAnnotation();
type = BuildTypeReference(singleAnn.typeName());
if (type == null) return null;
if (type == null)
{
var typeName = singleAnn.typeName().GetText();
type = new ClassOrInterfaceType(GetSourceRange(singleAnn.typeName()), typeName, null, new List<TypeArgument>(), new List<Annotation>());
}
if (singleAnn.elementValue() != null)
{
var value = BuildExpression(singleAnn.elementValue());
if (value != null)
var value = BuildElementValue(singleAnn.elementValue());
// Fallback if BuildElementValue returns null
if (value == null)
{
arguments.Add(new AnnotationValueArgument(
location, "value", value
));
var elementText = singleAnn.elementValue().GetText();
value = new LiteralExpression(GetSourceRange(singleAnn.elementValue()), elementText, LiteralKind.String);
}
arguments.Add(new AnnotationValueArgument(
location, "value", value
));
}
}
return type != null ? new Annotation(location, type, arguments) : null;
}
private Expression? BuildElementValue(Java9Parser.ElementValueContext context)
{
if (context.conditionalExpression() != null)
{
var expr = BuildExpression(context.conditionalExpression());
// If BuildExpression returns null, try to parse it as a string literal
if (expr == null)
{
var text = context.conditionalExpression().GetText();
if (text.StartsWith('"') && text.EndsWith('"'))
{
// Remove quotes and create a string literal
var value = text.Substring(1, text.Length - 2);
expr = new LiteralExpression(GetSourceRange(context), value, LiteralKind.String);
}
}
return expr;
}
else if (context.annotation() != null)
{
var annotation = BuildAnnotation(context.annotation());
if (annotation != null)
{
return new AnnotationExpression(GetSourceRange(context), annotation);
}
}
else if (context.elementValueArrayInitializer() != null)
{
var arrayInit = context.elementValueArrayInitializer();
var elements = new List<Expression>();
if (arrayInit.elementValueList() != null)
{
foreach (var elementValue in arrayInit.elementValueList().elementValue())
{
var element = BuildElementValue(elementValue);
if (element != null) elements.Add(element);
}
}
return new ArrayInitializer(GetSourceRange(arrayInit), elements);
}
return null;
}
private List<TypeParameter> BuildTypeParameters(Java9Parser.TypeParametersContext? context)
{
if (context == null) return new List<TypeParameter>();
@@ -414,8 +498,8 @@ namespace MarketAlly.IronJava.Core.AST.Builders
Java9Parser.ArrayTypeContext array => BuildArrayType(array),
Java9Parser.TypeVariableContext typeVar => BuildTypeVariable(typeVar),
Java9Parser.ClassTypeContext classType => BuildClassType(classType),
Java9Parser.InterfaceTypeContext interfaceType => BuildInterfaceType(interfaceType),
Java9Parser.TypeNameContext typeName => BuildTypeName(typeName),
Java9Parser.InterfaceTypeContext interfaceType => BuildInterfaceType(interfaceType),
_ => null
};
}
@@ -980,6 +1064,13 @@ namespace MarketAlly.IronJava.Core.AST.Builders
private Parameter? BuildLastParameter(Java9Parser.LastFormalParameterContext context)
{
// Check if this is just a regular parameter (not varargs)
if (context.formalParameter() != null)
{
return BuildParameter(context.formalParameter());
}
// Otherwise, it's a varargs parameter
var location = GetSourceRange(context);
var modifiers = BuildModifiers(context.variableModifier());
var annotations = BuildAnnotations(context.variableModifier());
@@ -989,7 +1080,7 @@ namespace MarketAlly.IronJava.Core.AST.Builders
var declaratorId = context.variableDeclaratorId();
var name = declaratorId.identifier().GetText();
var isFinal = modifiers.HasFlag(Modifiers.Final);
var isVarArgs = context.GetChild(context.ChildCount - 2)?.GetText() == "...";
var isVarArgs = true; // This branch is only for varargs
return new Parameter(location, type, name, isVarArgs, isFinal, annotations);
}
@@ -1325,7 +1416,7 @@ namespace MarketAlly.IronJava.Core.AST.Builders
if (context.defaultValue() != null)
{
defaultValue = BuildExpression(context.defaultValue().elementValue());
defaultValue = BuildElementValue(context.defaultValue().elementValue());
}
return new AnnotationMember(location, name, type, defaultValue);
@@ -2590,46 +2681,6 @@ namespace MarketAlly.IronJava.Core.AST.Builders
return parameters;
}
private Expression? BuildElementValue(Java9Parser.ElementValueContext context)
{
if (context.conditionalExpression() != null)
{
return BuildConditionalExpression(context.conditionalExpression());
}
else if (context.elementValueArrayInitializer() != null)
{
return BuildElementValueArrayInitializer(context.elementValueArrayInitializer());
}
else if (context.annotation() != null)
{
// Annotation as expression - wrap it
var annotation = BuildAnnotation(context.annotation());
if (annotation != null)
{
// Create a special expression to hold the annotation
return new IdentifierExpression(annotation.Location, "@" + annotation.Type.ToString());
}
}
return null;
}
private ArrayInitializer? BuildElementValueArrayInitializer(Java9Parser.ElementValueArrayInitializerContext context)
{
var location = GetSourceRange(context);
var elements = new List<Expression>();
if (context.elementValueList() != null)
{
foreach (var value in context.elementValueList().elementValue())
{
var expr = BuildElementValue(value);
if (expr != null) elements.Add(expr);
}
}
return new ArrayInitializer(location, elements);
}
// Statement building methods

View File

@@ -400,6 +400,23 @@ namespace MarketAlly.IronJava.Core.AST.Nodes
public override void Accept(IJavaVisitor visitor) => visitor.VisitArrayInitializer(this);
}
/// <summary>
/// Represents an annotation used as an expression (in annotation element values).
/// </summary>
public class AnnotationExpression : Expression
{
public Annotation Annotation { get; }
public AnnotationExpression(SourceRange location, Annotation annotation) : base(location)
{
Annotation = annotation;
AddChild(annotation);
}
public override T Accept<T>(IJavaVisitor<T> visitor) => visitor.VisitAnnotationExpression(this);
public override void Accept(IJavaVisitor visitor) => visitor.VisitAnnotationExpression(this);
}
/// <summary>
/// Represents a lambda expression.
/// </summary>

View File

@@ -51,6 +51,7 @@ namespace MarketAlly.IronJava.Core.AST.Visitors
void VisitNewExpression(NewExpression node);
void VisitNewArrayExpression(NewArrayExpression node);
void VisitArrayInitializer(ArrayInitializer node);
void VisitAnnotationExpression(AnnotationExpression node);
void VisitLambdaExpression(LambdaExpression node);
void VisitLambdaParameter(LambdaParameter node);
void VisitMethodReferenceExpression(MethodReferenceExpression node);
@@ -135,6 +136,7 @@ namespace MarketAlly.IronJava.Core.AST.Visitors
T VisitNewExpression(NewExpression node);
T VisitNewArrayExpression(NewArrayExpression node);
T VisitArrayInitializer(ArrayInitializer node);
T VisitAnnotationExpression(AnnotationExpression node);
T VisitLambdaExpression(LambdaExpression node);
T VisitLambdaParameter(LambdaParameter node);
T VisitMethodReferenceExpression(MethodReferenceExpression node);

View File

@@ -54,6 +54,7 @@ namespace MarketAlly.IronJava.Core.AST.Visitors
public virtual void VisitNewExpression(NewExpression node) => DefaultVisit(node);
public virtual void VisitNewArrayExpression(NewArrayExpression node) => DefaultVisit(node);
public virtual void VisitArrayInitializer(ArrayInitializer node) => DefaultVisit(node);
public virtual void VisitAnnotationExpression(AnnotationExpression node) => DefaultVisit(node);
public virtual void VisitLambdaExpression(LambdaExpression node) => DefaultVisit(node);
public virtual void VisitLambdaParameter(LambdaParameter node) => DefaultVisit(node);
public virtual void VisitMethodReferenceExpression(MethodReferenceExpression node) => DefaultVisit(node);
@@ -133,6 +134,7 @@ namespace MarketAlly.IronJava.Core.AST.Visitors
public virtual T VisitNewExpression(NewExpression node) => DefaultVisit(node);
public virtual T VisitNewArrayExpression(NewArrayExpression node) => DefaultVisit(node);
public virtual T VisitArrayInitializer(ArrayInitializer node) => DefaultVisit(node);
public virtual T VisitAnnotationExpression(AnnotationExpression node) => DefaultVisit(node);
public virtual T VisitLambdaExpression(LambdaExpression node) => DefaultVisit(node);
public virtual T VisitLambdaParameter(LambdaParameter node) => DefaultVisit(node);
public virtual T VisitMethodReferenceExpression(MethodReferenceExpression node) => DefaultVisit(node);

View File

@@ -8,7 +8,7 @@
<!-- NuGet Package Metadata -->
<PackageId>IronJava</PackageId>
<Version>2.1.1</Version>
<Version>2.1.2</Version>
<Authors>David H Friedel Jr</Authors>
<Company>MarketAlly</Company>
<Title>IronJava</Title>

View File

@@ -416,6 +416,22 @@ namespace MarketAlly.IronJava.Core.Serialization
return result;
}
public override Dictionary<string, object?> VisitAnnotationValueArgument(AnnotationValueArgument node)
{
var result = CreateBaseNode(node);
result["name"] = node.Name;
result["value"] = node.Value.Accept(this);
return result;
}
public override Dictionary<string, object?> VisitAnnotationArrayArgument(AnnotationArrayArgument node)
{
var result = CreateBaseNode(node);
result["name"] = node.Name;
result["values"] = node.Values.Select(v => v.Accept(this)).ToList();
return result;
}
public override Dictionary<string, object?> VisitJavaDoc(JavaDoc node)
{
var result = CreateBaseNode(node);
@@ -561,6 +577,13 @@ namespace MarketAlly.IronJava.Core.Serialization
return result;
}
public override Dictionary<string, object?> VisitAnnotationExpression(AnnotationExpression node)
{
var result = CreateBaseNode(node);
result["annotation"] = node.Annotation.Accept(this);
return result;
}
public override Dictionary<string, object?> VisitMethodReferenceExpression(MethodReferenceExpression node)
{
var result = CreateBaseNode(node);
@@ -747,6 +770,9 @@ namespace MarketAlly.IronJava.Core.Serialization
"InstanceOfExpression" => DeserializeInstanceOfExpression(element, location),
"NewArrayExpression" => DeserializeNewArrayExpression(element, location),
"ArrayInitializer" => DeserializeArrayInitializer(element, location),
"AnnotationExpression" => DeserializeAnnotationExpression(element, location),
"AnnotationValueArgument" => DeserializeAnnotationValueArgument(element, location),
"AnnotationArrayArgument" => DeserializeAnnotationArrayArgument(element, location),
"MethodReferenceExpression" => DeserializeMethodReferenceExpression(element, location),
"ClassLiteralExpression" => DeserializeClassLiteralExpression(element, location),
"LocalVariableStatement" => DeserializeLocalVariableStatement(element, location),
@@ -1282,6 +1308,30 @@ namespace MarketAlly.IronJava.Core.Serialization
return new ArrayInitializer(location, elements);
}
private AnnotationExpression DeserializeAnnotationExpression(JsonElement element, SourceRange location)
{
var annotation = Deserialize(element.GetProperty("annotation")) as Annotation ?? throw new JsonException("annotation is not Annotation");
return new AnnotationExpression(location, annotation);
}
private AnnotationValueArgument DeserializeAnnotationValueArgument(JsonElement element, SourceRange location)
{
var name = element.TryGetProperty("name", out var nameEl) && nameEl.ValueKind != JsonValueKind.Null
? nameEl.GetString()
: null;
var value = Deserialize(element.GetProperty("value")) as Expression ?? throw new JsonException("value is not Expression");
return new AnnotationValueArgument(location, name, value);
}
private AnnotationArrayArgument DeserializeAnnotationArrayArgument(JsonElement element, SourceRange location)
{
var name = element.TryGetProperty("name", out var nameEl) && nameEl.ValueKind != JsonValueKind.Null
? nameEl.GetString()
: null;
var values = DeserializeList<Expression>(element.GetProperty("values"));
return new AnnotationArrayArgument(location, name, values);
}
private MethodReferenceExpression DeserializeMethodReferenceExpression(JsonElement element, SourceRange location)
{
var target = Deserialize(element.GetProperty("target")) as Expression ?? throw new JsonException("target is not Expression");