Alors, comment puis-je écrire un processeur de symboles Kotlin (KSP) ?  |  par Pavlo Stavytskyï |  juillet 2021
Android

Alors, comment puis-je écrire un processeur de symboles Kotlin (KSP) ?

Le 30 juillet 2021 - 6 minutes de lecture

 

Toute la logique de génération de code de notre processeur sera implémentée dans Visitor Salle de classe.

Étape 1. Traitement des déclarations de classe

Notre processeur doit travailler avec des notes interfaces. Dans KSP, la déclaration d’interface est représentée par un KSClassDeclaration maquette. Nous commençons donc le traitement en visitant les déclarations de classe.

Aller à visitClassDeclaration méthode d’un Visitor et ajoutez le code d’implémentation.

Fonction Visitor.visitClassDeclaration (partie 1)

Généralement KSClassDeclaration décrit toutes sortes de classes, telles que class, data class, interface et d’autres. Nous devons donc nous assurer que la classe annotée est vraiment une interface vérifier le classKind biens. Sinon, nous arrêterons le traitement.

Ensuite, nous devons obtenir @Function annotation de la déclaration de classe. Dans Kotlin, les interfaces peuvent être annotées avec diverses annotations, comme nous le savons. Par conséquent, nous devons trouver le bon par son nom.

Ensuite, nous devons obtenir les informations des arguments d’annotation. Dans notre exemple, nous devons trouver le nom de la fonction générée en identifiant la valeur de la name argument de l’annotation.

Après cela, nous pouvons faire la vraie génération.

Continuez à mettre en œuvre visitClassDeclaration fonction avec le code ci-dessous.

Fonction Visitor.visitClassDeclaration (partie 2)

Tout d’abord, nous devons identifier tous les Propriétés que possède l’interface annotée. Ce seront les arguments d’une fonction générée.

Ensuite, nous commençons à générer la signature de la fonction en déléguant la génération d’arguments à visitPropertyDeclaration Occupation.

Enfin, lorsque la signature de fonction est générée, nous ajoutons un corps simple qui imprime Hello from <function_name> un message.

Étape 2. Traitement des réclamations de propriété

voyons comment visitPropertyDeclaration est implémenté.

Fonction Visitor.visitPropertyDeclaration

Pour chaque propriété, nous devons simplement générer des arguments de fonction dans un format. argName: ArgType.

Tandis que argName la partie est simple, il suffit d’obtenir simpleName propriété de la déclaration de propriété, le ArgType partie nécessite la prise en compte de 2 étapes supplémentaires.

d’abord, nous devons non seulement générer le type d’argument, mais nous devons également nous assurer qu’il est importé au fichier. Cela signifie que nous devons avoir le nom complet du type. Ceci peut être fait de deux façons:

  1. Utilisation de la déclaration d’importation :
    import com.package.ArgType.
  2. Utilisation du nom complet du type chaque fois que nous l’utilisons :
    argName: com.package.ArgType.

Nous utiliserons la deuxième option car elle est plus facile à mettre en œuvre. Nous n’aurons pas besoin d’aller et venir entre les déclarations d’importation et les endroits dans le code où le type est utilisé lors de la génération de code.

Alors, comment obtenons-nous le nom complet du type ?

si on utilise KSPropertyDeclaration.type, on obtient l’objet de type KSTypeReference . Cependant, pour plus d’informations sur le type, KSTypeReference nous devrions être trié appel resolve fonction qui retourne l’objet de type KSType.

Ainsi, lorsque vous avez un objet de type KSType, nous pouvons obtenir le nom complet en utilisant le code ci-dessous.

resolvedType.declaration.qualifiedName?.asString

Observation. O resolve Opération il est coûteux en calcul car le KSP devra aller chercher où le type est déclaré. Par conséquent, il doit être utilisé avec précaution.

En deuxième place, nous devons traiter les cas où le type a des paramètres génériques, tels que ArgType<String> . Cela nous oblige à implémenter des fonctionnalités supplémentaires.

Étape 3. Traitement des arguments de type générique

Pour chaque type d’argument que nous résolvons dans visitPropertyDeclaration nous devons résoudre séparément leurs arguments génériques qui sont déclarés entre crochets < >.

AuVisitor classe entre visitPropertyDeclaration et visitTypeArgument Les fonctions déclarent une fonction d’assistance appelée. visitTypeArguments (forme pluriel) qui prend la liste d’arguments de type.

Fonction Visitor.visitTypeArguments

La fonction ci-dessus définira le formatage du bloc d’arguments de type générique et parcourra chaque appel visitTypeArgument Occupation.

Commençons à mettre en œuvre le visitTypeArgument Occupation.

Fonction Visitor.visitTypeArgument (partie 1)

Lorsque vous travaillez avec des génériques dans Kotlin, nous devrions également considérer quelque chose appelé variance. (voir la documentation officielle de Kotlin pour en savoir plus).

Il y a 4 types de variation que nous devrions considérer :

  • StarType<*> (on vient de générer * symbole au lieu du type d’argument)
  • covariantType<out OtherType> (Nous ajoutons out avant le vrai type)
  • contravariantType<in OtherType> (Nous ajoutons in avant le vrai type)
  • invariantType<OtherType> (ajoutez simplement votre propre type d’argument)

Après avoir traité la variation de type, nous devons identifier le type réel de l’argument générique.

Fonction Visitor.visitTypeArgument (partie 2)

Nous appliquons la même approche que nous avons utilisée pour définir le type de propriété. Tout d’abord, nous résolvons KSTypeReferencepuis obtenez le nom complet de KSType:

resolvedType?.declaration?.qualifiedName?.asString()

N’oubliez pas de rendre le type d’argument nullable en ajoutant ?, si nécessaire.

Enfin, nous devons traiter les types d’arguments imbriqués tels que Type<OtherType<OtherType>>.

C’est une tâche très simple, car nous avons déjà mis en œuvre tous les composants nécessaires pour cela. il suffit d’appeler visitTypeArguments (forme pluriel) à l’intérieur visitTypeArgument pour ses types d’arguments imbriqués et fera le travail de manière récursive.

C’est tout ce dont nous avons besoin pour implémenter notre processeur. Maintenant, nous pouvons l’exécuter et voir comment cela fonctionne.

Ouvert main-project/src/main/kotlin/com/morfly/Main.kt fichier et remplacez son contenu par le code ci-dessous.

Exécution du processeur d’annotations

Nous y sommes presque…

Par défaut, votre IDE ne connaît rien du code généré avec KSP. Pour vous aider à reconnaître les fichiers générés, ouvrez main-project/build.gradle.kts et ajoutez le code ci-dessous quelque part dans le fichier.

Ajout de fichiers générés à l’ensemble source principal

Lors de l’ajout de ce paramètre, nous marquons explicitement un répertoire avec le code généré en tant que répertoire source Kotlin.

Observation. N’oubliez pas synchroniser Changements de niveau.

Exécutez maintenant le main rôle dans main-project/.../Main.kt Archiver.

Pour vérifier que la génération de code a réussi, ouvrez
main-project/build/generated/ksp/.../GeneratedFunctions.kt fichier et vérifiez son contenu.

fonctions générées

Félicitations, nous venons d’implémenter la fonctionnalité principale de notre processeur de symboles Kotlin !

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.