-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixup jmespath multiselect codegen #551
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,9 +21,9 @@ | |
import static software.amazon.smithy.go.codegen.util.ShapeUtil.BOOL_SHAPE; | ||
import static software.amazon.smithy.go.codegen.util.ShapeUtil.INT_SHAPE; | ||
import static software.amazon.smithy.go.codegen.util.ShapeUtil.STRING_SHAPE; | ||
import static software.amazon.smithy.go.codegen.util.ShapeUtil.listOf; | ||
import static software.amazon.smithy.utils.StringUtils.capitalize; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import software.amazon.smithy.codegen.core.CodegenException; | ||
|
@@ -64,6 +64,12 @@ public class GoJmespathExpressionGenerator { | |
|
||
private int idIndex = 0; | ||
|
||
// as we traverse an expression, we may produce intermediate "synthetic" lists - e.g. list of string, list of list | ||
// of string, etc. | ||
// we may need to pull the member shapes back out later, but they're not guaranteed to be in the model - so keep a | ||
// shadow map of synthetic -> member to short-circuit the model lookup | ||
private final Map<Shape, Shape> synthetics = new HashMap<>(); | ||
|
||
public GoJmespathExpressionGenerator(GoCodegenContext ctx, GoWriter writer) { | ||
this.ctx = ctx; | ||
this.writer = writer; | ||
|
@@ -121,8 +127,17 @@ private Variable visitMultiSelectList(MultiSelectListExpression expr, Variable c | |
var first = items.get(0); | ||
|
||
var ident = nextIdent(); | ||
writer.write("$L := []$P{$L}", ident, first.type, | ||
String.join(",", items.stream().map(it -> it.ident).toList())); | ||
writer.write("$L := []$T{}", ident, first.type); | ||
for (var item : items) { | ||
if (isPointable(item.type)) { | ||
writer.write(""" | ||
if $2L != nil { | ||
$1L = append($1L, *$2L) | ||
}""", ident, item.ident); | ||
} else { | ||
writer.write("$1L = append($1L, $2L)", ident, item.ident); | ||
} | ||
} | ||
|
||
return new Variable(listOf(first.shape), ident, sliceOf(first.type)); | ||
} | ||
|
@@ -247,11 +262,13 @@ private Variable visitProjection(ProjectionExpression expr, Variable current) { | |
writer.indent(); | ||
// projected.shape is the _member_ of the resulting list | ||
var projected = visit(expr.getRight(), new Variable(leftMember, "v", leftSymbol)); | ||
if (isPointable(lookahead.type)) { // projections implicitly filter out nil evaluations of RHS | ||
if (isPointable(lookahead.type)) { // projections implicitly filter out nil evaluations of RHS... | ||
var deref = lookahead.shape instanceof CollectionShape || lookahead.shape instanceof MapShape | ||
? "" : "*"; // ...but slices/maps do not get dereferenced | ||
writer.write(""" | ||
if $2L != nil { | ||
$1L = append($1L, *$2L) | ||
}""", ident, projected.ident); | ||
$1L = append($1L, $3L$2L) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: let's not do 1-3-2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pushed a commit to reflow this block to have the numbers go by order of first appearance, which coincidentally means that it's now append(2,3,1) but I think overall makes more sense. |
||
}""", ident, projected.ident, deref); | ||
} else { | ||
writer.write("$1L = append($1L, $2L)", ident, projected.ident); | ||
} | ||
|
@@ -348,22 +365,22 @@ private String nextIdent() { | |
return "v" + idIndex; | ||
} | ||
|
||
private Shape listOf(Shape shape) { | ||
var list = ShapeUtil.listOf(shape); | ||
synthetics.putIfAbsent(list, shape); | ||
return list; | ||
} | ||
|
||
private Shape expectMember(CollectionShape shape) { | ||
return switch (shape.getMember().getTarget().toString()) { | ||
case "smithy.go.synthetic#StringList" -> listOf(STRING_SHAPE); | ||
case "smithy.go.synthetic#IntegerList" -> listOf(INT_SHAPE); | ||
case "smithy.go.synthetic#BooleanList" -> listOf(BOOL_SHAPE); | ||
default -> ShapeUtil.expectMember(ctx.model(), shape); | ||
}; | ||
return synthetics.containsKey(shape) | ||
? synthetics.get(shape) | ||
: ShapeUtil.expectMember(ctx.model(), shape); | ||
} | ||
|
||
private Shape expectMember(MapShape shape) { | ||
return switch (shape.getValue().getTarget().toString()) { | ||
case "smithy.go.synthetic#StringList" -> listOf(STRING_SHAPE); | ||
case "smithy.go.synthetic#IntegerList" -> listOf(INT_SHAPE); | ||
case "smithy.go.synthetic#BooleanList" -> listOf(BOOL_SHAPE); | ||
default -> ShapeUtil.expectMember(ctx.model(), shape); | ||
}; | ||
return synthetics.containsKey(shape) | ||
? synthetics.get(shape) | ||
: ShapeUtil.expectMember(ctx.model(), shape); | ||
} | ||
|
||
// helper to generate comparisons from two results, automatically handling any dereferencing in the process | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,14 +23,11 @@ | |
import software.amazon.smithy.go.codegen.GoJmespathExpressionGenerator; | ||
import software.amazon.smithy.go.codegen.GoWriter; | ||
import software.amazon.smithy.go.codegen.SmithyGoTypes; | ||
import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; | ||
import software.amazon.smithy.jmespath.JmespathExpression; | ||
import software.amazon.smithy.model.node.Node; | ||
import software.amazon.smithy.model.shapes.OperationShape; | ||
import software.amazon.smithy.model.shapes.StructureShape; | ||
import software.amazon.smithy.rulesengine.language.EndpointRuleSet; | ||
import software.amazon.smithy.rulesengine.language.syntax.Identifier; | ||
import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType; | ||
import software.amazon.smithy.rulesengine.traits.ContextParamTrait; | ||
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; | ||
import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition; | ||
|
@@ -103,33 +100,14 @@ private GoWriter.Writable generateOperationContextParamBindings() { | |
} | ||
|
||
private GoWriter.Writable generateOpContextParamBinding(String paramName, OperationContextParamDefinition def) { | ||
var param = rules.getParameters().get(Identifier.of(paramName)).get(); | ||
var expr = JmespathExpression.parse(def.getPath()); | ||
|
||
return writer -> { | ||
var generator = new GoJmespathExpressionGenerator(ctx, writer); | ||
|
||
writer.write("func() {"); // contain the scope for each binding | ||
var result = generator.generate(expr, new GoJmespathExpressionGenerator.Variable(input, "in")); | ||
|
||
if (param.getType().equals(ParameterType.STRING_ARRAY)) { | ||
// projections can result in either []string OR []*string -- if the latter, we have to unwrap | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For context: discussed offline and this is done because projections don't have nulls, so they don't have |
||
var target = result.shape().asListShape().get().getMember().getTarget(); | ||
if (GoPointableIndex.of(ctx.model()).isPointable(target)) { | ||
writer.write(""" | ||
deref := []string{} | ||
for _, v := range $L { | ||
if v != nil { | ||
deref = append(deref, *v) | ||
} | ||
} | ||
p.$L = deref""", result.ident(), capitalize(paramName)); | ||
} else { | ||
writer.write("p.$L = $L", capitalize(paramName), result.ident()); | ||
} | ||
} else { | ||
writer.write("p.$L = $L", capitalize(paramName), result.ident()); | ||
} | ||
writer.write("p.$L = $L", capitalize(paramName), result.ident()); | ||
writer.write("}()"); | ||
}; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,15 +27,15 @@ | |
|
||
public final class ShapeUtil { | ||
public static final StringShape STRING_SHAPE = StringShape.builder() | ||
.id("smithy.api#String") | ||
.id("smithy.api#PrimitiveString") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know we talked about these offline, and since these tests work I guess this is fine, but I couldn't find any docs about the existence of this type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK I finally found it. It's in the prelude model, not the preamble model, I was searching the wrong thing - https://smithy.io/2.0/spec/model.html#prelude |
||
.build(); | ||
|
||
public static final IntegerShape INT_SHAPE = IntegerShape.builder() | ||
.id("smithy.api#Integer") | ||
.id("smithy.api#PrimitiveInteger") | ||
.build(); | ||
|
||
public static final BooleanShape BOOL_SHAPE = BooleanShape.builder() | ||
.id("smithy.api#Boolean") | ||
.id("smithy.api#PrimitiveBoolean") | ||
.build(); | ||
|
||
private ShapeUtil() {} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my general knowledge, but do shapes in the model usually get updated, or are they write-only? I would suspect that shapes are mostly a dictionary of things you've seen where you only ever add entries, but I'm not sure if they'd be a case where a shape would also get updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every construct in smithy in-code is immutable. The only way to change something is by going toBuilder() and then re-build()ing to create a new instance. So there's no way to just add these synthetics to the model in place (nor do I think we should, that would run the risk of them getting picked up elsewhere). Not sure if that answers your question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, but just to clarify. I'm not asking about the Smithy interface, I'm asking about the general mental model for building shapes
I'm not asking to add the synthetics into the model in place. We're building here a lookup map, and I was curious about the Smithy model allowed updates to existing model shapes, something like "you had shape A which was a list(string). Now it's a list(bool)`. This would mean that a lookup map may also need to be updated instead of just being write-once. I suspect this isn't valid just because I can't think of a use case where you'd want that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see. Yeah, once we're in this code, the model (and all of its shapes) are essentially fixed.