propensive / contextual Goto Github PK
View Code? Open in Web Editor NEWStatically-checked string interpolation in Scala
Home Page: https://soundness.dev/contextual/
License: Apache License 2.0
Statically-checked string interpolation in Scala
Home Page: https://soundness.dev/contextual/
License: Apache License 2.0
If I understand correctly, currently all compile time string interpolators defined using Contextual do not "treat" escapes. It seems to me like a fairly common use case to want the standard scala string escaping in one's compile time string interpolator.
Perhaps just an additional Boolean
parameter to the apply method of the Prefix
Object
would suffice. Implementation should not be too difficult as the String => String
function is exposed here in the std scala library.
import com.sksamuel.scrimage.Image
object RscInterpolator extends contextual.Interpolator {
override type Input = String
override type Output = Image
override def contextualize(interpolation: StaticInterpolation): Seq[ContextType] = {
val head = interpolation.literals.head
if (interpolation.macroContext.classPath.map(_.toString).contains(head)) Nil
else interpolation.abort(Literal(0, head), 0, "Can't find image on classpath.")
}
def evaluate(interpolation: RuntimeInterpolation): Output =
Image.fromResource(interpolation.literals.head)
}
the above code is intended to check at compile time for existence of files on class path. however, it doesn't compile for unclear reason:
[error] /Users/crimson/IdeaProjects/wmh-randomizer/macros/src/com/crimzie/wmh/util/RscInterpolator.scala:12:9: Symbol 'type scala.reflect.macros.whitebox.Context' is missing from the classpath.
[error] This symbol is required by 'value contextual.Interpolator.StaticInterpolation.macroContext'.
The return type of any Contextual value is fixed, but since the macro is transparent, it could be refined to a subtype. For example, in Guillotine the return type of a shell command such as sh"cat foo"
could be typed as Command["cat"]
instead of just Command
. In this example, it would provide some further opportunities to have type inference automatically choose suitable return types for execution.
Known literal Text
types from Gossamer could be refined to have the underlying String
literal type available as a member.
This would probably be best provided through a mixin trait along the lines of:
trait Precision[State, Return]:
interpolator: Interpolator[?, State, Return] =>
type Refined[S <: String & Singleton] <: Return
def result(state: State): String & Singleton
A user's implementation of result
would provide the String
based on the final State
value which would be passed into the Refined
type constructor to produce a new subtype of Return
.
An alternative approach may be to change the Return
type to Return[S <: String & Singleton]
in all cases, and to provide a default implementation of result
for users to override if they want this feature.
This looked promising:
class SqlInterpolator[T[_[_]]:BirecursiveT] {
object Expr extends StaticInterpolator[T[Sql]] {
def parse(s: String): String \/ T[Sql] =
parser[T].parseExpr(Query(s)).leftMap(parseError => s"Not a valid SQL expression: $parseError")
}
}
along with:
implicit class SqlStringContext(sc: StringContext) {
def sqlE[T[_[_]]: BirecursiveT] = Prefix((new SqlInterpolator[T]).Expr, sc)
def sqlB[T[_[_]]: BirecursiveT] = Prefix((new SqlInterpolator[T]).Blob, sc)
}
This particular example is attempting to define a String interpolator for a SQL AST defined in fix point style (for use with matryoshka) but really I think it illustrates the inability to make any kind of generic interpolator using contextual which is unfortunate because this is supported by Scala despite certain weird behaviors surrounding supplying a type parameter: https://stackoverflow.com/questions/44663377/is-it-possible-to-specify-type-parameters-for-string-interpolation-in-scala
This is the error that is generated at compile time with the above example:
java.lang.ClassNotFoundException: quasar.sql.SQLSpec.<local SQLSpec>.query.interpolator.type$
Presumably, caused by the macro implementation having trouble accessing the object inside the class. Maybe it's fundamentally impossible to get this to work in which case I think it should be clearly documented.
This is the definition of StaticInterpolator
not that it matters all that much to demonstrate the above point (as a side note this is pretty generic and could be added to contextual
itself minus the use of scalaz
):
trait StaticInterpolator[A] extends Interpolator {
def parse(s: String): String \/ A
def contextualize(interpolation: StaticInterpolation) = {
interpolation.parts.foreach {
case lit@Literal(_, string) =>
parse(string) match {
case -\/(msg) => interpolation.abort(lit, 0, msg)
case _ => ()
}
case hole@Hole(_, _) =>
interpolation.abort(hole, "substitutions are not supported")
}
Nil
}
// A String that would not parse should not have made it past compile time
def evaluate(interpolation: RuntimeInterpolation): A =
parse(interpolation.parts.mkString).valueOr(errMsg =>
scala.sys.error(s"Something terribly wrong happened. Failed to parse something at runtime that we checked at compile time. Reason: $errMsg. This is either because the `parse` function of this `StaticInterpolator` is not pure or could (less likely) be a bug with `StaticInterpolator`"))
}
The error message for a substitution with a wrong type is very cryptic:
import contextual.examples._
import shell._
val x=0
sh"$x"
will give the following compile error:
<console>:19: error: type mismatch;
found : Int
required: contextual.Interpolator.Embedded[String,_1.interpolator.type] where val _1: contextual.Prefix[contextual.examples.shell.ShellInterpolator.Output,contextual.examples.shell.ShellInterpolator.ContextType,contextual.examples.shell.ShellInterpolator.type]
sh"$x"
^
For a user of the contextual library this message can be very difficult to understand and it only indirectly gives a hint that the substituted value should be of type String
.
Is it possible to create a better message in this case?
Am I using it wrong or is it an error in the example?:
scala> bin"10111000"
<console>:35: error: type mismatch;
found : Array[Byte]
required: contextual.examples.binary.BinParser.Output
bin"10111000"
Very minor issue, but I think there may be some debugging statements left in. For example, with my interpolator:
scala> import org.phenoscape.sparql.SPARQLInterpolation._
import org.phenoscape.sparql.SPARQLInterpolation._
scala> val s = sparql"hello"
Literals: 'List(hello)'
s: org.phenoscape.sparql.SPARQLInterpolation.QueryText = QueryText(hello)
I don't expect to see Literals: 'List(hello)'
.
Thank you for contextual, it has served me well! I would like to make my interpolator available for Scala 2.13, but contextual 2.0.0 isn't working for me. I have a string interpolator for SPARQL queries, which embeds various types into SPARQL query strings with appropriate formatting. I upgraded to 2.0.0, and now I can't get any of my tests for interpolating various types to compile (phenoscape/sparql-utils#26).
There is a strong possibility I'm doing something wrong, but it has been working well for me with the previous version. I thought this might be related to #61.
Basically the only thing I changed in the upgrade is this line: https://github.com/phenoscape/sparql-utils/blob/7a58aec65caf1d296a01a2ba097b7d88411abdaa/modules/core/src/main/scala/org/phenoscape/sparql/SPARQLInterpolator.scala#L125
e.g. there is no v1.0.1
tag
Got a DNS_PROBE_FINISHED_NXDOMAIN
error
contextual is currently hardcoded to require the actual scala.StringContext
instead of a generating code that expects certain methods to exist.
This is problematic, since it means the user cannot provide a custom StringContext to (for example) disable or change the s
interpolator.
Link to http://co.ntextu.al/ does not work.
- That’s an error.
The requested URL / was not found on this server. That’s all we know.
scala-compiler
shouldn't be a compile dependency for a macro library. You probably meant to mark it (and, possibly, scala-reflect
) as Provided
, which is what circe does.
In neither the README nor https://propensive.com/opensource/contextual are there any usage instructions, like maven coordinates.
When the type of an expression that's substituted into an interpolated string is Nothing
, it probably means that the value is erroneous, and a message has already been emitted. If we can detect that the value is Nothing
as the result of an error, then we don't need to emit another message, since doing so distracts from the "real" error. But if the user really does try to substitute Nothing
into an interpolated string, we should emit an error.
If it's impossible to distinguish between Nothing
and and error, then we should update the error message to indicate that it's probably because of another error.
Amy plans for a Scala 2.13 release? Or would this be non-trivial?
this is basically nerd sniping of the highest order, but wouldn't it be nice to have a regex that linted its contents at compile time? I'm not aware of any such linter but surely there has got to be one.
Results in a ClassCastException
at compile time at the usage site if the StringContextOps
is defined in test files.
I really like to use your lib to do syntax checks on SQL/HQL queries. However, my queries are not pure strings, but rather interpolated strings with parameters, e.g.:
val id = 1
val query = q"from User where id = $id"
Would that be possible with contextual
? Is there any working example for such an interpolated string with arguments? So far I could only find the section Embedded Types
on https://propensive.com/opensource/contextual/#a-simple-example, but that's only very few information about how this works, and the example is unfortunately incomplete. A complete example would already help a lot.
The first example given on this page is out of date: https://propensive.com/opensource/contextual/#a-simple-example
Specifically, the line type Out = Url
should be type Output = Url
now. Also, that code block doesn't compile when you paste it into a file (for starters, it's missing a curly brace). It would be nice if you updated the docs to reflect the current code base and make the example work out of the box.
I have migrated the macro from Scala 2.12 to Scala 2.13, and cleaned some things up in the process. Notably, the Prefix
has been removed, and the instructions on binding the macro to an interpolator have changed. (The documentation and examples should reflect this.)
In the process, however, two of the tests failed to pass, with mysterious errors. While most extractors seem to work, this release may be useful for existing users who want to migrate to Scala 2.13, but it includes a regression over the previous release, and should not be considered bug-free.
This issue is a reminder that more work needs to be done to investigate this problem.
Reproduced here: https://scalafiddle.io/sf/WOGG0M5/1
Similar to issue #4
From Gitter:
Andrea Peruffo @andreaTP 20:53
@mkotsbak since scala.js 0.6.15 basic reflection instantiation is supported
sjrd/scala-js@67f7413
but the code will differ from JS to JVM in this case
don't just spoof with 2.11
Code to do this exists in Guillotine (and may soon exist in Nettlesome), but could be generalized.
When calling the macro, the companion object needs to be specified twice. It's difficult, but it ought to be possible to avoid this requirement.
Contextual should support compile-time-checked extractor interpolations, too, for example
myJson match {
case json"""{ "key": $value }""" => value
}
It should even be possible to offer typed extraction by specifying a type ascription, like so:
myJson match {
case json"""{ "key": ${value: Int} }""" => value
}
At the moment the latest of the available layers is QmdJxSATboyipFr8HkukeoRWqmVTW64V8FdWiUHriyTbZb
. This layer defines only one module ("core"), but doesn't contain definitions of the "data" module (or "tests" for that matter). The string interpolators from this module, such as scalac
and fqt
, are used by tests in the Magnolia project.
Macros are supported, and uses no other dependencies, so I assume it should be easy to cross build for Scala.Js too.
See http://stackoverflow.com/questions/30143126/how-do-i-port-an-existing-scala-library-to-scalajs
The five methods required to implement an interpolator could be much simplified in many cases with a subtype of Interpolator
which doesn't deal with intermediate state or substitution types.
Scala and Java both have lots of types that are initialised from a string, where an illegal string will compile but fail at runtime. It'd be great if Contextual could provide standard modules for such types.
I've often felt the need for such a tool for:
java.net.URL
java.net.URI
I'm sure there are a lot commonly used types out there that would benefit from a literal syntax.
Happy to lend a hand or even write most of these, but if you feel this is a good idea, the structure needs to come from you - do you want that to be a completely separate library that depends on Contextual? Standard Contextual modules that live in this repository?
Due, it seems, to some quirks of reflection in the REPL, calls to Class.forName
do not appear to load the requested class, if it has just been defined. This means that currently it's not possible to define an interpolator and use it in the REPL.
just copy what I did... JetBrains/intellij-scala#388
This relies upon propensive/rudiments#4.
I have the usecase where we are using contextual to generate "keys" for a nested logging framework. We are using contextual to ensure that the keys are alphanumeric.
We also have generic derivation of ADTs, e.g. to generate the keys from the case class fields.
However, although we have the fields of the case classes, we can't currently run the contextualize
validation phase on those keys so we are falling back to hacks 😭 (e.g. runtime exceptions)
It would be good to be able to pass a Name <: Symbol
through a contextualize
validation phase so we can get a compilation failure if a field that is invalid. No interpolation is needed, it's just a String
that we're validating.
I use this a lot, might be useful to you
abstract class UnsafeVerifier[A] extends Verifier[A] {
override def check(s: String): Either[(Int, String), A] =
Try(attempt(s)).toEither.left.map(_ => (0, fail))
def attempt(s: String): A
def fail: String
}
It's not really "unsafe" because it is called at compiletime.
Happy to help with this, but it needs to happen, 1.0.1 was published 2 months ago :)
As an inner class, Scala can't pattern match on it.
package com.mobimeo.ticketissuer.primitives
import contextual._
object Test {
val NonEmptyString = NonEmptyStringModule.Instance
type NonEmptyString = NonEmptyString.Type
sealed abstract class NonEmptyStringModule {
type Type <: String
def apply(string: String): Option[NonEmptyString] =
NonEmptyStringParser.check(string).toOption
}
object NonEmptyStringModule {
val Instance: NonEmptyStringModule = new NonEmptyStringModule {
type Type = String
}
}
object NonEmptyStringParser extends Verifier[NonEmptyString] {
def check(string: String): Either[(Int, String), NonEmptyString] =
if (string.isEmpty()) Left(0 -> "string must not be empty") else Right(string.asInstanceOf[NonEmptyString])
}
implicit class NonEmptyStringContext(sc: StringContext) {
val n = Prefix(NonEmptyStringParser, sc)
}
n"foo"
}
This gives me an Error:(31, 3) macro has not been expanded n"foo"
It did work in Scala 2.12.x
Substitution types are currently just String
s. This is convenient in some ways because it's easy to lift a singleton string literal type to its string value, but the number of strings is infinite, and overrepresent the possible kinds of substitution that can be made (which is typically about two or three). There are also no guardrails to help implementors of given Substitution
s specify the correct type.
Lines 200-212 of interpolator.scala
use forName
and getField
to access the name of the context by its string name. This could be avoided by just constructing a Select
tree at compile time, and referring directly to the object.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.