-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat: fromXxx() methods can be called with implicit or explicit parameters #309
Conversation
): MethodCall { | ||
const candidateFQN = assertFullyQualifiedStaticMethodCall(call); | ||
const candidateClass = typeSystem.findClass(candidateFQN); | ||
const methods = enumLikeClassMethods(candidateClass); | ||
const methods = staticNonVoidMethods(candidateClass); |
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.
Because methods like Bucket.fromBucketName()
return an IBucket
and thus get filtered out in enumLikeClassMethods()
.
src/type-resolution/callables.ts
Outdated
function prepareParameters( | ||
x: ArrayLiteral, | ||
callable: reflect.Callable, | ||
logicalId?: string |
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.
This is the place where the logical id is used. Every other function in this PR that got logicalId
added to its signature only passes it down in order to reach this point. I'm not very happy with this situation, but I haven't found a good way to solve it.
One option I considered was to not receive the logical id here and, instead, inject a new pseudo-parameter (something like CDK::ImplicitId
) in the second argument. Then, at the point where the lazy resource is resolved, this pseudo-parameter would be replaced with the id. But this would obscure the logic by creating a tight coupling between two functions that know nothing about each other.
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.
I'm not sure which way is better. But I do share your feelings about it being passed down all the way.
Re: pseudo-parameter. I don't think it's too bad to do this. Also it would not have to be a pseudo-parameter, but could be an AST type lazy-logical-id
. We already have lazy evaluated resources, so why not have a lazy id?
Another option would be in introduce a context to type resolution. We will likely need something like it for #246 anyway. Basically a generic means of keeping track of where in the AST we are, what logicalID, what property, what the parent type is, etc.
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.
Thanks for the suggestion. I agree with introducing context to type resolution. We should probably address that in its own change. For now, I've created a new intrinsic that lazily resolves the id. By default, it throws the error with the information we have. When it makes sense (static method calls on lazy resources) we replace that implementation with one that actually returns the id.
if (isBehavioralInterface(typeRef.type)) { | ||
return ResolvableExpressionType.BEHAVIORAL_INTERFACE; | ||
} | ||
if (isSerializableInterface(typeRef.type)) { |
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.
This is bug fix. Many behavioral interfaces are being classified as structs because of the order of evaluation.
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.
Do we have an explicit test case for this? If not maybe a comment would be useful.
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.
Added.
}); | ||
|
||
expect(() => new TypedTemplate(template, { typeSystem })).toThrow( | ||
/failed because the id could not be inferred/ |
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.
I suppose we could try and generate an id even for cases like this. For example, the id could be the logical id of the resource + the property name (BucketencryptionKey
in this case). But this would be a complex change in itself (and I'm not even sure this can be done for all cases).
This is a one way door decision. Allowing implicit calls later would not be a breaking change.
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.
To clarify: Does the explicit version with CDK:Args
work here?
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.
Yes. Added a new test for this scenario.
src/evaluate/evaluate.ts
Outdated
if (logicalId === 'CDK::Scope') { | ||
return this.context.stack; | ||
} |
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.
I guess this works. The other Pseudo Parameters are added to the context as evaluation references. Did you try to do that? I'd expect that to work without adding the implemenation detail to resolveReferences
.
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.
Ah, that's better. Thanks!
src/parser/template/resource.ts
Outdated
if (!/^[A-Za-z0-9]+$/.test(logicalId)) { | ||
throw new Error( | ||
`Resource logical IDs must be alphanumeric. Got: '${logicalId}'.` | ||
); | ||
} |
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.
Do we need this?
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.
I guess not. CFN will already have done this validation.
src/type-resolution/callables.ts
Outdated
function prepareParameters( | ||
x: ArrayLiteral, | ||
callable: reflect.Callable, | ||
logicalId?: string |
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.
I'm not sure which way is better. But I do share your feelings about it being passed down all the way.
Re: pseudo-parameter. I don't think it's too bad to do this. Also it would not have to be a pseudo-parameter, but could be an AST type lazy-logical-id
. We already have lazy evaluated resources, so why not have a lazy id?
Another option would be in introduce a context to type resolution. We will likely need something like it for #246 anyway. Basically a generic means of keeping track of where in the AST we are, what logicalID, what property, what the parent type is, etc.
if (isBehavioralInterface(typeRef.type)) { | ||
return ResolvableExpressionType.BEHAVIORAL_INTERFACE; | ||
} | ||
if (isSerializableInterface(typeRef.type)) { |
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.
Do we have an explicit test case for this? If not maybe a comment would be useful.
}); | ||
|
||
expect(() => new TypedTemplate(template, { typeSystem })).toThrow( | ||
/failed because the id could not be inferred/ |
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.
To clarify: Does the explicit version with CDK:Args
work here?
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.
See inline comments/questions.
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.
Love it! 😍
Static factory methods with a “from” prefix allow users to import unowned constructs. Awslint enforces the rules that the first two arguments are the scope and id, followed by the parameters specific to the method.
With this change, users can call methods of this category, by either omitting the scope and id:
or by passing them explicitly using a new CDK-specific intrinsic function,
CDK::Args
, along with a new CDK-specific pseudo-parameter,CDK::Scope
: