Skip to content

Instantly share code, notes, and snippets.

@Simn
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Simn/9475605 to your computer and use it in GitHub Desktop.
Save Simn/9475605 to your computer and use it in GitHub Desktop.
Haxe @:deprecated test
-cp src
-main Main
-js bin/js.js
--macro DeprecationMacro.use()
--no-output
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);
}
}
}
@: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