Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Make MultiTargetAnnotation type-parameterized and fix some comments #1230

Open
wants to merge 2 commits into
base: master-deprecated
Choose a base branch
from

Conversation

jwright6323
Copy link
Contributor

@jwright6323 jwright6323 commented Nov 8, 2019

This PR adds a type parameter to MultiTargetAnnotation (#1109) to allow concrete subclasses to have stricter type checking of targets, if desired. I also cleaned up a few comment nits.

Checklist

  • Did you specify the type of improvement?
  • Did you state the API impact?
  • Did you specify the code generation impact?
  • Did you request a desired merge strategy?

Type of Improvement

  • code refactoring
  • new feature/API

API Impact

API modification, but you can use the old behavior by using Target as type parameter T. This allows stricter type checking of subclasses of MultiTargetAnnotation if desired.

Backend Code Generation Impact

No change

Desired Merge Strategy

  • Squash: The PR will be squashed and merged (choose this if you have no preference.

@jwright6323 jwright6323 requested a review from a team as a code owner November 8, 2019 18:14
@CLAassistant
Copy link

CLAassistant commented Nov 8, 2019

CLA assistant check
All committers have signed the CLA.

azidar
azidar previously approved these changes Nov 14, 2019
@albert-magyar
Copy link
Contributor

One thing to point out is that this does have an API impact, since people would have to change code that extends MultiTargetAnnotation. That should be fine for 1.3 (master), though, especially since it's new and doesn't appear in the repo.


/** Assume [[RenameMap]] is `Map(TargetA -> Seq(TargetA1, TargetA2, TargetA3), TargetB -> Seq(TargetB1, TargetB2))`
* in the update, this Annotation is still one annotation, but the contents are renamed in the below form
* Seq(Seq(TargetA1, TargetA2, TargetA3), Seq(TargetB1, TargetB2), Seq(TargetC))
**/
def update(renames: RenameMap): Seq[Annotation] = Seq(duplicate(targets.map(ts => ts.flatMap(renames(_)))))
def update(renames: RenameMap): Seq[Annotation] = Seq(duplicate(targets.map(_.flatMap(renames(_).map(_.asInstanceOf[T])))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might give a cryptic error if a rename violates the type constraints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggested change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think about non-intrusive ways to hide it from the user, but the TLDR is that casts to erased types can lead to class case exceptions in the caller's code. The normal Scala way to solve this is with ClassTags, but this is complicated for a trait and can clutter downstream code.

If someone uses a RenameMap that has a record that renames a ReferenceTarget to an InstanceTarget or something, it would produce some crazy results.

@jwright6323
Copy link
Contributor Author

jwright6323 commented Nov 14, 2019

One thing to point out is that this does have an API impact, since people would have to change code that extends MultiTargetAnnotation. That should be fine for 1.3 (master), though, especially since it's new and doesn't appear in the repo.

@albert-magyar wouldn't it infer the type parameter to be Target?

Edit: never mind, I get what you are saying now.

@albert-magyar
Copy link
Contributor

@albert-magyar wouldn't it infer the type parameter to be Target?

Unfortunately, it won't generally infer the type parameter for the trait, since Scala only has local type inference. Not saying that it's a problem (I personally don't think it is), but just clarifying for the sake of the issue template classification text.

@jwright6323
Copy link
Contributor Author

@albert-magyar Cool, I edited the classification in the first comment.

@albert-magyar
Copy link
Contributor

I'll preface this by saying that I don't think this is a showstopper by any means, but more of a heads-up about the potential holes in the type safety of this parameter.

As a concrete example of renames that do happen that could lead to type errors, Inline adds a rename record mapping from an InstanceTarget to a ModuleTarget. This would be unchecked within the update method of a MultiTargetAnnoation[InstanceTarget] that previously included an inlined instance.

The annoying part is that, type erasure can cause the ClassCastException to appear at some distant, indefinite point in the compiler, since the cast actually occurs outside the generic! The default solution is to use a ClassTag in the scope of the method, which allows type-casing code to actually be checked at runtime.

renames(target).map {
  case t: T => t.asInstanceOf[T]  // needs ClassTag to actually be checked!
  case _ => // handle error
}

Unfortunately, using ClassTags generally requires peppering your code with implicit values. A lot of the ways to reduce the associated boilerplate of adding these implicits are hampered by the fact that the API is already inherited from Annotation, which lacks type parameters, and by the fact that MultiTargetAnnotation is a trait.

@jwright6323
Copy link
Contributor Author

@albert-magyar What would happen if you used a SingleTargetAnnotation[InstanceTarget] on an Inline module? Is there a reason that would work and this would not, or is this problem endemic to both annotations?

@albert-magyar
Copy link
Contributor

SingleTargetAnnotation has a specific check for illegal renames.

It's generally pretty ugly, but the TLDR is that it does a duplicate call with the actual returned target inside update to provoke the cast to happen at that point in time, allowing the exception to be contained within the scope of update. Also, it more-or-less depends on the user extending SingleTargetAnnotation with a non-generic subtype (i.e. X extends SingleTargetAnnotation[ModuleTarget]) to work effectively.

The problem is that the contents of a MultiTargetAnnotation are not T, and not even a generic on T, but a generic on a generic on T, which means that similar code would be much more complex, and would not be able to rely on a simple duplicate being implemented by inheriting classes, even if they themselves are not generic.

@jackkoenig jackkoenig dismissed azidar’s stale review November 18, 2019 20:44

Needs more thought, see Albert's comments

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants