Last active
August 29, 2015 13:57
-
-
Save Simn/9475605 to your computer and use it in GitHub Desktop.
Haxe @:deprecated test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-cp src | |
-main Main | |
-js bin/js.js | |
--macro DeprecationMacro.use() | |
--no-output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import haxe.macro.Context; | |
import haxe.macro.Expr; | |
import haxe.macro.Type; | |
using haxe.macro.Tools; | |
using Lambda; | |
class DeprecationMacro { | |
/** | |
This method registers our handler via `Context.onGenerate`. It is | |
activated from command line as `--macro DeprecationMacro.use()`. | |
**/ | |
static public function use() { | |
Context.onGenerate(checkDeprecation); | |
} | |
/** | |
This is the callback which is invoked by the compiler after being | |
registered via `Context.onGenerate(checkDeprecation)` in the `use` | |
method. | |
It receives an argument `types` which is a collection of all types known | |
to the compiler just before generation. Note that this is the state | |
after DCE has run, so some types may have been filtered. | |
**/ | |
static function checkDeprecation(types:Array<Type>) { | |
/* | |
It is not necessary to create an instance in the general case, but | |
it's useful here because we want to maintain some state. | |
*/ | |
new DeprecationMacro(types); | |
} | |
/** | |
A reference to the current class type. We use this to avoid printing | |
deprecation errors when a class accesses its own fields. | |
**/ | |
var currentClass:ClassType; | |
/** | |
A reference to the current class field. This can be used to log where | |
the access to a deprecated field is made. | |
**/ | |
var currentField:ClassField; | |
/** | |
Our constructor iterates over all received types and matches them | |
against `TInst(c, _)`, which represents all class types. For each of | |
those it calls `handleField`. | |
**/ | |
function new(types:Array<Type>) { | |
for (type in types) { | |
switch (type) { | |
case TInst(c, _): | |
var c = c.get(); | |
currentClass = c; | |
// Iterate over all member fields. | |
for (cf in c.fields.get()) { | |
handleField(cf); | |
} | |
// Iterate over all static fields. | |
for (cf in c.statics.get()) { | |
handleField(cf); | |
} | |
// Handle the constructor if available. | |
if (c.constructor != null) { | |
handleField(c.constructor.get()); | |
} | |
// Handle initialization expression (`__init__`) if available. | |
if (c.init != null) { | |
handleExpr(c.init); | |
} | |
case _: | |
} | |
} | |
} | |
/** | |
Checks if `c` is our current class. Note that we cannot check for | |
physical equality with `==` here because the instances of the class type | |
might come from separate `c.get()` calls. Instead we compare the class | |
path. | |
**/ | |
function isCurrentClass(c:ClassType) { | |
return c.name == currentClass.name && c.pack.join(".") == currentClass.pack.join("."); | |
} | |
/** | |
Checks if `metadata` has a `@:deprecated` entry and emits it as a | |
warning. | |
**/ | |
function getDeprecationMessage(metadata:MetaAccess, kind:String, posUsage:Position, posDefinition:Position) { | |
if (!metadata.has(":deprecated")) { | |
return; | |
} | |
// Use Lambda.find to extract deprecated entry. | |
var deprecatedMeta = metadata.get().find(function (metaEntry:MetadataEntry) return metaEntry.name == ":deprecated"); | |
// Check if entry has a string argument. | |
var message = switch (deprecatedMeta.params) { | |
case [{expr: EConst(CString(s))}]: s; | |
case _: 'Usage of this $kind is deprecated'; | |
} | |
Context.warning(message, posUsage); | |
} | |
/** | |
Checks if class `c` is deprecated. | |
**/ | |
function checkClass(c:ClassType, p:Position) { | |
if (isCurrentClass(c)) { | |
return; | |
} | |
getDeprecationMessage(c.meta, "class", p, c.pos); | |
} | |
/** | |
Checks if class field `cf` is deprecated. | |
**/ | |
function checkField(cf:ClassField, p:Position) { | |
getDeprecationMessage(cf.meta, "field", p, cf.pos); | |
} | |
/** | |
Checks if module type `mt` is deprecated. | |
**/ | |
function checkModuleType(mt:ModuleType, p:Position) { | |
switch (mt) { | |
case TClassDecl(c): checkClass(c.get(), p); | |
// TODO: TEnumDecl | |
case _: | |
} | |
} | |
/** | |
Walks expression `e`, checking for any deprecated field access or usage | |
of deprecated types. | |
Usually this would be an iterating method of type `TypedExpr -> Void`, | |
but currently `TypedExprTools` only has a `map` method, so we use that | |
instead. | |
**/ | |
function handleExpr(e:TypedExpr) { | |
return switch (e.expr) { | |
case TField(e1, fa): | |
// Don't forget to loop into the sub-expression here. | |
handleExpr(e1); | |
switch (fa) { | |
case FStatic(c, cf) | FInstance(c, cf): | |
// Static or member field access, check the class and the field | |
checkClass(c.get(), e.pos); | |
checkField(cf.get(), e.pos); | |
case FAnon(cf): | |
// Field access on anonymous structure, check the field. | |
checkField(cf.get(), e.pos); | |
case FClosure(c, cf): | |
// Closure access, check the field and also check the class if available | |
if (c != null) { | |
checkClass(c.get(), e.pos); | |
} | |
checkField(cf.get(), e.pos); | |
// TODO: FEnum | |
case _: | |
} | |
e; | |
case TNew(c, _, el): | |
// Again, don't forget to loop into the argument sub-expressions. | |
for (e in el) { | |
handleExpr(e); | |
} | |
var c = c.get(); | |
// Check the class | |
checkClass(c, e.pos); | |
// If the class has a constructor, check that too. This does not | |
// account for parent class constructors at the moment. | |
if (c.constructor != null) { | |
checkField(c.constructor.get(), e.pos); | |
} | |
e; | |
case TTypeExpr(mt) | TCast(_, mt) if (mt != null): | |
// TTypeExpr is a type identifier such as `Main`. | |
// We also check the `cast(_, Type)` case. | |
checkModuleType(mt, e.pos); | |
e; | |
case _: | |
e.map(handleExpr); | |
} | |
} | |
/** | |
Sets the current field to `cf` and check its expression if available. | |
**/ | |
function handleField(cf:ClassField) { | |
currentField = cf; | |
var expr = cf.expr(); | |
if (expr != null) { | |
handleExpr(expr); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@:deprecated("Enum warning") | |
enum E { | |
A; | |
} | |
enum E2 { | |
@:deprecated("Enum field warning") | |
B; | |
} | |
@:deprecated("Class warning") | |
class Main { | |
static function main() { | |
Test; // Class warning | |
cast(null, Test); // Class warning | |
Main; // No warning, same class | |
new Main(); // Constructor warning | |
Main.staticField(); // Static field warning | |
Main.staticField2(); // "Usage of this field is deprecated" | |
Test.staticField(); // Class warning | |
new Test(); // Class warning | |
E; // Enum warning | |
B; // Enum field warning | |
} | |
@:deprecated("Constructor warning") | |
function new() { | |
memberField; // Member field warning | |
this; // No warning, same class | |
} | |
@:deprecated("Static field warning") | |
static function staticField() { } | |
@:deprecated | |
static function staticField2() { } | |
@:deprecated("Member field warning") | |
function memberField() { } | |
} | |
@:deprecated("Class warning") | |
class Test { | |
@:deprecated("Class field warning") | |
static public function staticField() { | |
} | |
@:deprecated("Constructor warning") | |
public function new() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment