you probably don’t want it to have all the same methods as CharSequence
or String
, as so many of those methods return one of those types.
The TaintedS version of the CharSequence/String methods all return TaintedS results instead of Strings, and many are overloaded to take either a String or a TaintedS. They are just there so that you can use a TaintedS in the most familiar possible way (like a String) but safely.
Having .toString()
throw an exception would break the contract and be likely to cause lots of collateral damage; probably better to have it return one of your special encodings.
The .toString() implementation is just as you suggest:
@Deprecated("Don't use this accidentally - Encode before using!")
override fun toString(): String = "⛔$str⛔"
/**
* Using this trusts user input, defeating the purpose of this
* class, but if you really want that, here it is. Tip: search
* your project for usages of this method!
*/
override fun unsafeRaw(): String = str
I still wish this could be prevented at compile-time because inevitably, someone writes:
"Hello " + myTaintedString
And you have to look for the No-Entry signs at runtime to know anything has gone wrong. I guess somehow preventing the implicit String conversion on a class would solve my strongest motivating issue. But there are other motivations…
What would that give you over the existing solution of making it an unrelated type?
Consider a library with an Interface: AlternatingCurrent. The library has sub-interfaces TwoPhase and ThreePhase, Ac120V and Ac240V. You are writing a method .foo()
and want to allow any combination of these except ThreePhase. If you only cared about runtime, you could use an exception:
fun foo(ac: AlternatingCurrent) {
if (ac is ThreePhase) {
throw IllegalStateException("Can't handle ThreePhase.");
}
// do stuff...
}
To see this at compile time, you could use an imaginary Union typealias, essentially allowing one of the three types:
typealias AcNot3Phase = (TwoPhase | Ac210V | Ac240V)
fun foo(ac: AcNot3Phase) {
// do stuff...
}
But if there is ever another AlternatingCurrent sub-interface, such as Ac110V, this method won’t handle it. Using an intersection type with the imaginary NOT would allow additional sub-interfaces to pass:
fun <T> foo(ac: T) where T : AlternatingCurrent, T : !ThreePhase {
// do stuff...
}
Like with the Expression Problem, both of the above solutions have loopholes. Still, I think they could be useful. I guess I’m asking for intersection types and a NOT type. Or an alternative that I can’t imagine at the moment…