From 47e21178cccc27664bb669bd7ab9812f9bfcac44 Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 5 Dec 2024 15:37:55 -0800 Subject: [PATCH] [J2KT] Report `INFO` in `InsertCastsOnNullabilityMismatch` pass when a cast is inserted due to nullability mismatch, with source and target type descriptors. The description contains information about resolved nullability and captures. PiperOrigin-RevId: 703274571 --- .../java/com/google/j2cl/common/Problems.java | 5 + .../InsertCastsOnNullabilityMismatch.java | 152 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/transpiler/java/com/google/j2cl/common/Problems.java b/transpiler/java/com/google/j2cl/common/Problems.java index a88faa5b57..c1bb317864 100644 --- a/transpiler/java/com/google/j2cl/common/Problems.java +++ b/transpiler/java/com/google/j2cl/common/Problems.java @@ -176,6 +176,11 @@ private void problem(Severity severity, String message) { problemsBySeverity.put(severity, message); } + @FormatMethod + public void info(SourcePosition sourcePosition, String detailMessage, Object... args) { + problem(Severity.INFO, sourcePosition, detailMessage, args); + } + @FormatMethod public void info(String detailMessage, Object... args) { problem(Severity.INFO, String.format(detailMessage, args)); diff --git a/transpiler/java/com/google/j2cl/transpiler/passes/InsertCastsOnNullabilityMismatch.java b/transpiler/java/com/google/j2cl/transpiler/passes/InsertCastsOnNullabilityMismatch.java index 4715472eef..da12b02235 100644 --- a/transpiler/java/com/google/j2cl/transpiler/passes/InsertCastsOnNullabilityMismatch.java +++ b/transpiler/java/com/google/j2cl/transpiler/passes/InsertCastsOnNullabilityMismatch.java @@ -16,6 +16,7 @@ package com.google.j2cl.transpiler.passes; import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -26,12 +27,15 @@ import com.google.j2cl.transpiler.ast.DeclaredTypeDescriptor; import com.google.j2cl.transpiler.ast.Expression; import com.google.j2cl.transpiler.ast.IntersectionTypeDescriptor; +import com.google.j2cl.transpiler.ast.NullabilityAnnotation; import com.google.j2cl.transpiler.ast.PrimitiveTypeDescriptor; import com.google.j2cl.transpiler.ast.TypeDescriptor; import com.google.j2cl.transpiler.ast.TypeDescriptors; import com.google.j2cl.transpiler.ast.TypeVariable; import com.google.j2cl.transpiler.ast.UnionTypeDescriptor; import com.google.j2cl.transpiler.passes.ConversionContextVisitor.ContextRewriter; +import java.util.ArrayList; +import java.util.List; import javax.annotation.Nullable; /** Inserts casts in places where necessary due to nullability differences in type arguments. */ @@ -68,6 +72,14 @@ public Expression rewriteTypeConversionContext( return expression; } + Describer describer = new Describer(); + getProblems() + .info( + getSourcePosition(), + "Inserted nullability mismatch cast from '%s' to '%s'", + describer.getDescription(expression.getTypeDescriptor()), + describer.getDescription(inferredTypeDescriptor)); + return CastExpression.newBuilder() .setExpression(expression) .setCastTypeDescriptor(castTypeDescriptor) @@ -276,4 +288,144 @@ private enum Variance { OUT, IN_OUT } + + /** + * Produces readable description string for type descriptors, containing information about + * resolved nullability and captures with unique IDs. + */ + // TODO(b/382500942): Remove when no longer needed for debugging / development. + private static final class Describer { + private final List seenCaptures = new ArrayList<>(); + + @Nullable + private static String getDescription(NullabilityAnnotation nullabilityAnnotation) { + switch (nullabilityAnnotation) { + case NULLABLE: + return "@Nullable"; + case NONE: + return null; + case NOT_NULLABLE: + return "@NonNull"; + } + throw new AssertionError(); + } + + private String getDescription(TypeDescriptor typeDescriptor) { + return getDescription(typeDescriptor, ImmutableList.of()); + } + + private String getDescription( + TypeDescriptor typeDescriptor, ImmutableList enclosingWildcardOrCaptures) { + if (typeDescriptor instanceof PrimitiveTypeDescriptor) { + PrimitiveTypeDescriptor primitiveTypeDescriptor = (PrimitiveTypeDescriptor) typeDescriptor; + return primitiveTypeDescriptor.getSimpleSourceName(); + } else if (typeDescriptor instanceof ArrayTypeDescriptor) { + ArrayTypeDescriptor arrayTypeDescriptor = (ArrayTypeDescriptor) typeDescriptor; + return getDescription( + arrayTypeDescriptor.getComponentTypeDescriptor(), enclosingWildcardOrCaptures) + + getDescriptionInfix(getNullabilityAnnotation(typeDescriptor)) + + "[]"; + } else if (typeDescriptor instanceof DeclaredTypeDescriptor) { + DeclaredTypeDescriptor declaredTypeDescriptor = (DeclaredTypeDescriptor) typeDescriptor; + return getDescriptionPrefix(getNullabilityAnnotation(typeDescriptor)) + + declaredTypeDescriptor.getTypeDeclaration().getReadableDescription() + + getTypeArgumentsDescription(declaredTypeDescriptor, enclosingWildcardOrCaptures); + } else if (typeDescriptor instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) typeDescriptor; + return getDescriptionPrefix(getNullabilityAnnotation(typeDescriptor)) + + getDescriptionWithoutNullabilityAnnotation(typeVariable, enclosingWildcardOrCaptures); + } else if (typeDescriptor instanceof IntersectionTypeDescriptor) { + IntersectionTypeDescriptor intersectionTypeDescriptor = + (IntersectionTypeDescriptor) typeDescriptor; + return intersectionTypeDescriptor.getIntersectionTypeDescriptors().stream() + .map(it -> getDescription(it, enclosingWildcardOrCaptures)) + .collect(joining(" & ", "(", ")")); + } else if (typeDescriptor instanceof UnionTypeDescriptor) { + UnionTypeDescriptor unionTypeDescriptor = (UnionTypeDescriptor) typeDescriptor; + return unionTypeDescriptor.getUnionTypeDescriptors().stream() + .map(it -> getDescription(it, enclosingWildcardOrCaptures)) + .collect(joining(" | ", "(", ")")); + } else { + throw new AssertionError(); + } + } + + private String getTypeArgumentsDescription( + DeclaredTypeDescriptor declaredTypeDescriptor, + ImmutableList enclosingWildcardOrCaptures) { + ImmutableList arguments = declaredTypeDescriptor.getTypeArgumentDescriptors(); + if (arguments.isEmpty()) { + return ""; + } else { + return arguments.stream() + .map(it -> getDescription(it, enclosingWildcardOrCaptures)) + .collect(joining(", ", "<", ">")); + } + } + + private String getDescriptionWithoutNullabilityAnnotation( + TypeVariable typeVariable, ImmutableList enclosingWildcardOrCapture) { + if (!typeVariable.isWildcardOrCapture()) { + return typeVariable.getName(); + } else { + return getCaptureDescription(typeVariable) + + getBoundDescription(typeVariable, enclosingWildcardOrCapture); + } + } + + private String getCaptureDescription(TypeVariable typeVariable) { + if (!typeVariable.isCapture()) { + return ""; + } + + int index = seenCaptures.indexOf(typeVariable); + if (index == -1) { + index = seenCaptures.size(); + seenCaptures.add(typeVariable); + } + return "capture#" + (index + 1) + "-of "; + } + + private String getBoundDescription( + TypeVariable typeVariable, ImmutableList enclosingWildcardOrCaptures) { + int index = enclosingWildcardOrCaptures.indexOf(typeVariable); + if (index != -1) { + return "rec#" + (enclosingWildcardOrCaptures.size() - index); + } + enclosingWildcardOrCaptures = + ImmutableList.builder() + .addAll(enclosingWildcardOrCaptures) + .add(typeVariable) + .build(); + TypeDescriptor lowerBound = typeVariable.getLowerBoundTypeDescriptor(); + if (lowerBound != null) { + return "? super " + getDescription(lowerBound, enclosingWildcardOrCaptures); + } else { + return "? extends " + + getDescription( + typeVariable.getUpperBoundTypeDescriptor(), enclosingWildcardOrCaptures); + } + } + + private static NullabilityAnnotation getNullabilityAnnotation(TypeDescriptor typeDescriptor) { + if (typeDescriptor instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) typeDescriptor; + return typeVariable.getNullabilityAnnotation(); + } else { + return typeDescriptor.isNullable() + ? NullabilityAnnotation.NULLABLE + : NullabilityAnnotation.NOT_NULLABLE; + } + } + + private static String getDescriptionPrefix(NullabilityAnnotation nullabilityAnnotation) { + String description = getDescription(nullabilityAnnotation); + return description == null ? "" : description + " "; + } + + private static String getDescriptionInfix(NullabilityAnnotation nullabilityAnnotation) { + String description = getDescription(nullabilityAnnotation); + return description == null ? "" : " " + description + " "; + } + } }