Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gemini/GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Project Instructions
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import lombok.Builder;
import lombok.EqualsAndHashCode;

import java.util.Collections;
import java.util.List;
import java.util.Map;


/**
* Represents a generic type variable.
* <br>
Expand All @@ -21,7 +24,8 @@ public final class TypeVariableInfo extends ReferableTypeInfo {
private final String contextTypeQualifiedName; // TODO: reference to TypeDef to avoid string
private final String name;
private String qualifiedName;
// TODO: support generic bounds
@Builder.Default
private final List<TypeInfo> bounds = Collections.emptyList();

public static String concatQualifiedName(String contextTypeQualifiedName, String name) {
return contextTypeQualifiedName + "@" + name;
Expand All @@ -35,6 +39,10 @@ public String name() {
return name;
}

public List<TypeInfo> bounds() {
return bounds;
}

public String qualifiedName() {
if (qualifiedName == null) {
qualifiedName = concatQualifiedName(contextTypeQualifiedName, name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package online.sharedtype.it.java8;

import online.sharedtype.SharedType;

@SharedType
public class TypeBoundsIssue7 {
@SharedType
public interface Shape {
double area();
}

@SharedType
public static class Circle implements Shape {
public double radius;

@Override
public double area() {
return 3.14 * radius * radius;
}
}

@SharedType
public static class ContainerBounds<T extends Shape> {
public T shape;
}

@SharedType
public static class MultiBoundContainer<T extends Shape & java.io.Serializable> {
public T shape;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,28 @@ private List<TypeVariableInfo> parseTypeVariables(TypeElement typeElement) {
TypeVariableInfo.builder()
.contextTypeQualifiedName(typeElement.getQualifiedName().toString())
.name(typeParameterElement.getSimpleName().toString())
.bounds(parseBounds(typeParameterElement, typeElement))
.build()
)
.collect(Collectors.toList()); // TODO: type bounds
.collect(Collectors.toList());
}

private List<TypeInfo> parseBounds(TypeParameterElement typeParameterElement, TypeElement typeElement) {
return typeParameterElement.getBounds().stream()
.filter(b -> !"java.lang.Object".equals(b.toString()))
.filter(b -> {
Element e = ctx.getProcessingEnv().getTypeUtils().asElement(b);
if (e == null || ctx.isIgnored(e)) {
return false;
}
if (e instanceof TypeElement) {
TypeElement te = (TypeElement) e;
return !ctx.isOptionalType(te.getQualifiedName().toString());
}
return true;
})
.map(b -> typeInfoParser.parse(b, typeElement))
.collect(Collectors.toList());
}

private List<TypeInfo> parseSupertypes(TypeElement typeElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,20 @@ public Tuple<Template, AbstractTypeExpr> convert(TypeDef typeDef) {
ClassDef classDef = (ClassDef) typeDef;
StructExpr value = new StructExpr(
classDef.simpleName(),
classDef.typeVariables().stream().map(typeInfo -> typeExpressionConverter.toTypeExpr(typeInfo, typeDef)).collect(Collectors.toList()),
classDef.typeVariables().stream().map(typeVar -> {
String name = typeVar.name();
if (typeVar.bounds().isEmpty()) {
return name + " any";
}
String bounds = typeVar.bounds().stream()
.map(b -> typeExpressionConverter.toTypeExpr(b, typeDef))
.collect(Collectors.joining("; "));

if (typeVar.bounds().size() > 1) {
return name + " interface{ " + bounds + " }";
}
return name + " " + bounds;
}).collect(Collectors.toList()),
classDef.directSupertypes().stream().map(typeInfo1 -> typeExpressionConverter.toTypeExpr(typeInfo1, typeDef)).collect(Collectors.toList()),
gatherProperties(classDef)
);
Expand Down Expand Up @@ -64,7 +77,7 @@ String typeParametersExpr() {
if (typeParameters.isEmpty()) {
return null;
}
return String.format("[%s any]", String.join(", ", typeParameters));
return String.format("[%s]", String.join(", ", typeParameters));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,16 @@ public Tuple<Template, AbstractTypeExpr> convert(TypeDef typeDef) {
ClassDef classDef = (ClassDef) typeDef;
StructExpr value = new StructExpr(
classDef.simpleName(),
classDef.typeVariables().stream().map(typeInfo -> typeExpressionConverter.toTypeExpr(typeInfo, typeDef)).collect(Collectors.toList()),
classDef.typeVariables().stream().map(typeVar -> {
String name = typeVar.name();
if (typeVar.bounds().isEmpty()) {
return name;
}
String bounds = typeVar.bounds().stream()
.map(b -> typeExpressionConverter.toTypeExpr(b, typeDef))
.collect(Collectors.joining(" + "));
return name + ": " + bounds;
}).collect(Collectors.toList()),
gatherProperties(classDef),
rustMacroTraitsGenerator.generate(classDef)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ public Tuple<Template, AbstractTypeExpr> convert(TypeDef typeDef) {
Config config = ctx.getTypeStore().getConfig(typeDef);
InterfaceExpr value = new InterfaceExpr(
classDef.simpleName(),
classDef.typeVariables().stream().map(typeInfo -> typeExpressionConverter.toTypeExpr(typeInfo, typeDef)).collect(Collectors.toList()),
classDef.typeVariables().stream().map(typeVar -> {
String name = typeVar.name();
if (typeVar.bounds().isEmpty()) {
return name;
}
String bounds = typeVar.bounds().stream()
.map(b -> typeExpressionConverter.toTypeExpr(b, typeDef))
.collect(Collectors.joining(" & "));
return name + " extends " + bounds;
}).collect(Collectors.toList()),
classDef.directSupertypes().stream().map(typeInfo1 -> typeExpressionConverter.toTypeExpr(typeInfo1, typeDef)).collect(Collectors.toList()),
classDef.components().stream().map(field -> toPropertyExpr(field, typeDef, config)).collect(Collectors.toList())
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,60 @@ void ignoreGlobalConfiguredField() {
var classDef = parser.parse(typeElement).get(0);
assertThat(classDef.components()).isEmpty();
}

@Test
void parseTypeBounds() {
var boundTypeMock = ctxMocks.typeElement("com.example.Bound");
var boundType = boundTypeMock.type();
var typeParam = ctxMocks.typeParameter("T");

java.util.List<javax.lang.model.type.TypeMirror> bounds = new java.util.ArrayList<>();
bounds.add(boundType);
org.mockito.Mockito.doReturn(bounds).when(typeParam.element()).getBounds();

var clazz = ctxMocks.typeElement("com.example.MyClass")
.withTypeParameters(typeParam.element())
.element();

var parsedBoundType = ConcreteTypeInfo.builder().qualifiedName("com.example.Bound").build();
when(typeInfoParser.parse(boundType, clazz)).thenReturn(parsedBoundType);

var parsedSelfTypeInfo = ConcreteTypeInfo.builder().qualifiedName("com.example.MyClass").build();
when(typeInfoParser.parse(clazz.asType(), clazz)).thenReturn(parsedSelfTypeInfo);

var classDefs = parser.parse(clazz);
var classDef = (ClassDef)classDefs.get(0);

assertThat(classDef.typeVariables()).hasSize(1);
var typeVar = classDef.typeVariables().get(0);
assertThat(typeVar.name()).isEqualTo("T");
assertThat(typeVar.bounds()).containsExactly(parsedBoundType);
}

@Test
void parseTypeBoundsWithObject() {
var objectTypeMock = ctxMocks.typeElement("java.lang.Object");
var objectType = objectTypeMock.type();
var typeParam = ctxMocks.typeParameter("T");

java.util.List<javax.lang.model.type.TypeMirror> bounds = new java.util.ArrayList<>();
bounds.add(objectType);
org.mockito.Mockito.doReturn(bounds).when(typeParam.element()).getBounds();
when(objectType.toString()).thenReturn("java.lang.Object");

var clazz = ctxMocks.typeElement("com.example.MyClass")
.withTypeParameters(typeParam.element())
.element();

var parsedSelfTypeInfo = ConcreteTypeInfo.builder().qualifiedName("com.example.MyClass").build();
when(typeInfoParser.parse(clazz.asType(), clazz)).thenReturn(parsedSelfTypeInfo);

var classDefs = parser.parse(clazz);
var classDef = (ClassDef)classDefs.get(0);

assertThat(classDef.typeVariables()).hasSize(1);
var typeVar = classDef.typeVariables().get(0);
assertThat(typeVar.name()).isEqualTo("T");
assertThat(typeVar.bounds()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void convert() {
assertThat(data).isNotNull();
var model = (GoStructConverter.StructExpr) data.b();
assertThat(model.name).isEqualTo("ClassA");
assertThat(model.typeParameters).containsExactly("T");
assertThat(model.typeParameters).containsExactly("T any");
assertThat(model.typeParametersExpr()).isEqualTo("[T any]");
assertThat(model.supertypes).containsExactly("SuperClassA[string]");

Expand Down Expand Up @@ -115,6 +115,33 @@ void convert() {
assertThat(prop5.type).isEqualTo("map[string]int32");
}

@Test
void convertTypeWithBounds() {
ClassDef classDef = ClassDef.builder()
.simpleName("ClassA")
.qualifiedName("com.github.cuzfrog.ClassA")
.typeVariables(List.of(
TypeVariableInfo.builder()
.name("T")
.bounds(List.of(
ConcreteTypeInfo.builder()
.qualifiedName("com.github.cuzfrog.Shape")
.simpleName("Shape")
.build()
))
.build()
))
.components(List.of())
.build();

var data = converter.convert(classDef);
var model = (GoStructConverter.StructExpr) data.b();

assertThat(model.name).isEqualTo("ClassA");
assertThat(model.typeParameters).containsExactly("T Shape");
assertThat(model.typeParametersExpr()).isEqualTo("[T Shape]");
}

@Test
void inlineTagsOverrideDefaultTags() {
var propertyExpr = new GoStructConverter.PropertyExpr(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,30 @@ void convertComplexType() {
assertThat(prop4.optional).isFalse();
assertThat(prop4.typeExpr()).isEqualTo("String");
}

@Test
void convertTypeWithBounds() {
ClassDef classDef = ClassDef.builder()
.simpleName("ClassA")
.qualifiedName("com.github.cuzfrog.ClassA")
.typeVariables(List.of(
TypeVariableInfo.builder()
.name("T")
.bounds(List.of(
ConcreteTypeInfo.builder()
.qualifiedName("com.github.cuzfrog.Shape")
.simpleName("Shape")
.build()
))
.build()
))
.components(List.of())
.build();

var data = converter.convert(classDef);
var model = (RustStructConverter.StructExpr) data.b();

assertThat(model.name).isEqualTo("ClassA");
assertThat(model.typeParameters).containsExactly("T: Shape");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,33 @@ void optionalFieldUnionNullAndUndefined() {
assertThat(prop1.unionUndefined).isTrue();
assertThat(prop1.readonly).isFalse();
}

@Test
void writeInterfaceWithBounds() {
ClassDef classDef = ClassDef.builder()
.qualifiedName("com.github.cuzfrog.ClassA")
.simpleName("ClassA")
.typeVariables(Collections.singletonList(
TypeVariableInfo.builder()
.name("T")
.bounds(Collections.singletonList(
ConcreteTypeInfo.builder()
.qualifiedName("com.github.cuzfrog.Shape")
.simpleName("Shape")
.build()
))
.build()
))
.components(Collections.emptyList())
.build();

when(ctxMocks.getContext().getTypeStore().getConfig(classDef)).thenReturn(config);
when(config.getTypescriptFieldReadonly()).thenReturn(Props.Typescript.FieldReadonlyType.NONE);

var tuple = converter.convert(classDef);
assertThat(tuple).isNotNull();
TypescriptInterfaceConverter.InterfaceExpr model = (TypescriptInterfaceConverter.InterfaceExpr) tuple.b();
assertThat(model.name).isEqualTo("ClassA");
assertThat(model.typeParameters).containsExactly("T extends Shape");
}
}
Loading