Skip to content
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

Embedding for Strings #244

Open
wants to merge 14 commits into
base: formal-verification
Choose a base branch
from

Conversation

GrigoriiSolnyshkin
Copy link
Collaborator

This pull request introduces embeddings for Kotlin types Char and String.
Chars are translated to Viper integers, whereas Strings are represented by viper sequences.

Note that String is a sequence of viper Ints rather than Refs as this approach should be more lightweight.

Operations implemented:

  • comparisons for characters
  • arithmetic for characters
  • operator get for strings
  • concatenation of strings
  • literals for char and strings

Apart from that, this PR

  • implements builder for registration of Kotlin function which we want to handle separately (and their implementation)
  • adds functions which are implemented by special ExpEmbeddings only sometimes (depending on the arguments passed)
  • implements builder for "operator" ExpEmbeddings

Overall, to add some operator on the primitive types it generally needs to be registered twice from now on (rather than thrice or more) and with some quite simple builders

Copy link
Owner

@jesyspa jesyspa left a comment

Choose a reason for hiding this comment

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

Looks good! There are a few things where I feel like more clarity would help, and I wonder whether we could have fewer special cases.

val callableType = callableType ?: error("Signature not specified to buildExpEmbedding.")
val argumentTypes = callableType.formalArgTypes
val returnType = callableType.returnType
check(argumentTypes.size == 1 || argumentTypes.size == 2) { "Operator should take exactly two arguments." }
Copy link
Owner

Choose a reason for hiding this comment

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

nit: error doesn't match with condition.

}

class StringEmbeddingDetails(type: ClassTypeEmbedding) : ClassEmbeddingDetails(type, false) {

Copy link
Owner

Choose a reason for hiding this comment

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

nit: imho starting a class with an empty line like this looks sloppy, does the autoformatter not remove it?

import org.jetbrains.kotlin.utils.addToStdlib.ifTrue

class ClassEmbeddingDetails(val type: ClassTypeEmbedding, val isInterface: Boolean) : TypeInvariantHolder {
open class ClassEmbeddingDetails(val type: ClassTypeEmbedding, val isInterface: Boolean) : TypeInvariantHolder {
Copy link
Owner

Choose a reason for hiding this comment

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

I am not sure that inheritance is a good idea here. While we are in a situation now where the special cases are all dijsoint, perhaps they could be given some other way, like via constructor parameters or using a method call?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We're redefining an implementation of one of the methods. It seems kind of like inheritance...
We could do sealed base and default / string implementation. Anyway, I don't like how I inherit from the extension function of some builder...

Copy link
Owner

Choose a reason for hiding this comment

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

I think the key point is that you aren't really specialising the whole thing, so using composition (with something that might use inheritance itself) would feel more natural/clean to me.

@@ -41,6 +43,16 @@ internal class ClassPredicateBuilder private constructor(private val details: Cl
body.addAll(builder.toAssertionsList())
}

fun forFieldNamed(name: String, action: FieldAssertionsBuilder.() -> Unit) {
when (val field = details.fields[SimpleKotlinName(Name.identifier(name))]) {
Copy link
Owner

Choose a reason for hiding this comment

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

Should it be an error if this is used with a non-user field embedding?

Comment on lines +71 to +72
else RuntimeTypeDomain.classTypeFunc(name)
Copy link
Owner

Choose a reason for hiding this comment

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

Does the string class need this amount of special treatment? I feel like it's a class like any other when it comes to RTTI

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess we could do the same with String not being special here.
We could do the same with Booleans, Ints and Chars as well.
It may have some sense to reduce the amount of code we need to add a new primitive type.
On the other hand, it feels nice when these type embeddings have short beautiful names.
My thought regarding String was that it is treated somewhat special in Kotlin (having String literals etc.) and that is the main reason I decided to do it like this

/**
* Functions that should sometimes be handled separately by our conversion depending on arguments they're called with.
*/
interface PartiallySpecialKotlinFunction : SeparatelyHandledKotlinFunction {
Copy link
Owner

Choose a reason for hiding this comment

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

I don't quite understand the way this class is meant to be used. Do I understand correctly that baseEmbedding is the function that is called if no special case is triggered, and tryInsertCall is what decides whether the special behaviour should be used or not? Is this all being introduced only for the sake of StringPlusAnyFunction, or are there other cases?

import org.jetbrains.kotlin.formver.viper.MangledName


interface SeparatelyHandledKotlinFunction : FunctionEmbedding {
Copy link
Owner

Choose a reason for hiding this comment

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

Could you elaborate on what you mean by SeparatelyHandled? How is it different from Special?

import org.jetbrains.kotlin.formver.linearization.LinearizationContext
import org.jetbrains.kotlin.formver.viper.ast.Exp

interface InjectionBasedExpEmbedding : DirectResultExpEmbedding {
Copy link
Owner

Choose a reason for hiding this comment

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

Could you elaborate on the sense in which it is InjectionBased? How should people understand the members?

builtinsOperation(inner.toViperBuiltinType(ctx), pos = ctx.source.asPosition, info = sourceRole.asInfo)
}

class OperatorExpEmbeddingTemplate(val type: TypeEmbedding, val refsOperation: InjectionImageFunction) {
Copy link
Owner

Choose a reason for hiding this comment

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

When we pick an operator, it seems like we should already know whether it's binary or unary. Why allow for both types of calls on this type, instead of having two types?

Copy link
Owner

@jesyspa jesyspa left a comment

Choose a reason for hiding this comment

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

LGTM, but please wait for Ilya's feedback.

Comment on lines +48 to +49
val byName: Map<MangledName, FunctionEmbedding>
get() = buildList {
Copy link
Owner

Choose a reason for hiding this comment

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

Please make this a function instead of a property with a getter.

@@ -49,7 +41,10 @@ import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
*/
class ProgramConverter(val session: FirSession, override val config: PluginConfiguration, override val errorCollector: ErrorCollector) :
ProgramConversionContext {
private val methods: MutableMap<MangledName, FunctionEmbedding> = SpecialKotlinFunctions.byName.toMutableMap()
private val methods: MutableMap<MangledName, FunctionEmbedding> =
SpecialKotlinFunctions.byName.toMutableMap().also {
Copy link
Owner

Choose a reason for hiding this comment

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

Not super important, but it's a bit weird that we are making a map, then turning it into a mutable map, and then mutating it again. If this is what we'll do with SpecialKotlinFunctions.byName anyway, maybe let's have it take a mutable map/mutable map builder? That would also make it clear it is stateless.

Comment on lines +125 to +129
when {
existing is PartiallySpecialKotlinFunction ->
existing.also { it.initBaseEmbedding(userFunction) }
else -> userFunction.also { methods[symbol.embedName(this)] = it }
}
Copy link
Owner

Choose a reason for hiding this comment

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

nit: it's a bit strange to have when here when you use if above for almost the same check.

val signature = embedFullSignature(symbol)
val callable = processCallable(symbol, signature)
UserFunctionEmbedding(callable)
override fun embedFunction(symbol: FirFunctionSymbol<*>): FunctionEmbedding {
Copy link
Owner

Choose a reason for hiding this comment

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

This code here seems to be the reason to have PartiallySpecialKotlinFunction. Let us imagine we decided to only use SpecialKotlinFunction, that would sometimes request an embedding. What piping are we missing for that? I assume ProgramConversionContext would have to be passed somewhere where it currently isn't.


fun SpecialKotlinFunction.embedName(): ScopedKotlinName = callableId.embedFunctionName(callableType)

object SpecialKotlinFunctions {
Copy link
Owner

Choose a reason for hiding this comment

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

Please add a comment that explains that here we provide ExpEmbedding implementations of built-in Kotlin functions.

name: String,
body: (List<ExpEmbedding>, StmtConversionContext) -> ExpEmbedding,
) {

Copy link
Owner

Choose a reason for hiding this comment

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

nit: unnecessary newline

body: (List<ExpEmbedding>, StmtConversionContext) -> ExpEmbedding,
) {

val newFunction = object : FullySpecialKotlinFunction {
Copy link
Owner

Choose a reason for hiding this comment

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

At this point, should FullySpecialKotlinFunction not have an implementation which this thing passes all the data to, rather than creating a new object for each case? I'm not sure what the trade-offs are. (Maybe FullySpecialKotlinFunction should just be a class?)

}

data object NullLit : PureExpEmbedding {
data object NullLit : LiteralEmbedding {

Copy link
Owner

Choose a reason for hiding this comment

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

nit: unnecessary newline

Comment on lines +14 to +16
sealed class ClassPredicateEnhancer {
internal abstract fun applyAdditionalAssertions(builder: ClassPredicateBuilder)
}
Copy link
Owner

Choose a reason for hiding this comment

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

Why not an interface?

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

Successfully merging this pull request may close these issues.

2 participants