Skip to content
5 changes: 3 additions & 2 deletions dmd/astenums.d
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,21 @@ enum STC : ulong // transfer changes to declaration.h
live = 0x10_0000_0000_0000, /// function `@live` attribute
register = 0x20_0000_0000_0000, /// `register` storage class (ImportC)
volatile_ = 0x40_0000_0000_0000, /// destined for volatile in the back end
ctonly = 0x80_0000_0000_0000, /// function that is only called during compile time

safeGroup = STC.safe | STC.trusted | STC.system,
IOR = STC.in_ | STC.ref_ | STC.out_,
TYPECTOR = (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild),
FUNCATTR = (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.property | STC.live |
safeGroup),
safeGroup | STC.ctonly),

/* These are visible to the user, i.e. are expressed by the user
*/
visibleStorageClasses =
(STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.const_ | STC.final_ | STC.abstract_ | STC.synchronized_ |
STC.deprecated_ | STC.future | STC.override_ | STC.lazy_ | STC.alias_ | STC.out_ | STC.in_ | STC.manifest |
STC.immutable_ | STC.shared_ | STC.wild | STC.nothrow_ | STC.nogc | STC.pure_ | STC.ref_ | STC.return_ | STC.tls | STC.gshared |
STC.property | STC.safeGroup | STC.disable | STC.local | STC.live),
STC.property | STC.safeGroup | STC.disable | STC.local | STC.live | STC.ctonly),

/* These storage classes "flow through" to the inner scope of a Dsymbol
*/
Expand Down
3 changes: 2 additions & 1 deletion dmd/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@ struct IntRange;
#define STClive 0x10000000000000ULL /// function `@live` attribute
#define STCregister 0x20000000000000ULL /// `register` storage class (ImportC)
#define STCvolatile 0x40000000000000ULL /// destined for volatile in the back end
#define STCctonly 0x80000000000000ULL /// function that is only called during compile time

#define STC_TYPECTOR (STCconst | STCimmutable | STCshared | STCwild)
#define STC_FUNCATTR (STCref | STCnothrow | STCnogc | STCpure | STCproperty | STCsafe | STCtrusted | STCsystem)
#define STC_FUNCATTR (STCref | STCnothrow | STCnogc | STCpure | STCproperty | STCsafe | STCtrusted | STCsystem | STCctonly)

void ObjectNotFound(Identifier *id);

Expand Down
62 changes: 62 additions & 0 deletions dmd/expression.d
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,61 @@ extern (C++) /* IN_LLVM abstract */ class Expression : ASTNode
return false;
}

/*********************************************
* Calling function f.
* Check the @__ctfe attribute, i.e. we can only call @__ctfe functions
* from either other @__ctfe functions or ctfe.
* Returns true if error occurs.
*/
bool checkCtonly(FuncDeclaration f, ref Loc loc, Scope* sc, bool fIsAliasParam)
{
auto fTy = f.type.toTypeFunction();
if (!fTy) {
warning("callee `%s` is not a function?", f.toPrettyChars());
return false;
}
if (!fTy.isCtonly()) return false;
if (sc.flags & SCOPE.ctfe) return false;
auto caller = sc.func;
if (!caller) {
error("cannot call @__ctfe function `%s` from non-CTFE context", f.toPrettyChars());
return true;
}
auto callerTy = caller.type.toTypeFunction();
if (!callerTy) {
warning("caller `%s` is not a function?", caller.toPrettyChars());
return false;
}
if (callerTy.isCtonly()) return false;
if (fIsAliasParam && caller.isInstantiated()) {
// message(loc, "restricting %s to be @__ctfe, since it calls @__ctfe function %s it got via an alias parameter",
// caller.toPrettyChars(), f.toPrettyChars());
callerTy.isCtonly = true;
callerTy.isCtonlyInferred = true;
callerTy.ctOnlyInferReason = f;
return false;
}
if (fTy.isCtonlyInferred()) {
if (caller.isInstantiated()) {
// Propagate inferred @__ctfe to all templated functions.
// This is not great: should instead check that the original infer reason
// is in template parameters, or in parameters of parameters and so on...
// message(loc, "propagating @__ctfe to %s, since it calls function %s that was inferred to be @__ctfe",
// caller.toPrettyChars(), f.toPrettyChars());
callerTy.isCtonly = true;
callerTy.isCtonlyInferred = true;
callerTy.ctOnlyInferReason = fTy.ctOnlyInferReason;
return false;
}
}
error("cannot call @__ctfe function `%s` from non-@__ctfe function `%s`", f.toPrettyChars(), sc.func.toPrettyChars());
if (fTy.isCtonlyInferred()) {
errorSupplemental("`%s` was inferred to be @__ctfe because it (transitively) calls `%s`",
f.toPrettyChars(), fTy.ctOnlyInferReason.toPrettyChars());
}
return true;
}

/********************************************
* Check that the postblit is callable if t is an array of structs.
* Returns true if error happens.
Expand Down Expand Up @@ -3732,6 +3787,13 @@ extern (C++) final class VarExp : SymbolExp
error("cannot modify operator `$`");
return ErrorExp.get();
}
if (auto fd = var.isFuncDeclaration()) {
auto fty = fd.type.toTypeFunction();
if (fty.isCtonly()) {
error("cannot take address of CTFE-only function `%s`", var.toChars());
return ErrorExp.get();
}
}
return this;
}

Expand Down
22 changes: 18 additions & 4 deletions dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -4205,6 +4205,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return; // semantic() already run
}

bool fIsAliasParam = false;
Objects* tiargs = null; // initial list of template arguments
Expression ethis = null;
Type tthis = null;
Expand Down Expand Up @@ -4255,6 +4256,18 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return;
}

// That's a hacky way to check if exp.e1 is an alias template parameter.
// I found that such aliases have parent == null, but I'm not sure if
// this guarantees that it's a template parameter.
if (IdentifierExp ie = exp.e1.isIdentifierExp()) {
Dsymbol parentSc;
Dsymbol s = sc.search(ie.loc, ie.ident, &parentSc);
if (s) {
if (auto ae = s.isAliasDeclaration()) {
if (!ae.parent) fIsAliasParam = true;
}
}
}
/* This recognizes:
* foo!(tiargs)(funcargs)
*/
Expand Down Expand Up @@ -4710,7 +4723,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
}

if (!exp.ignoreAttributes)
checkFunctionAttributes(exp, sc, exp.f);
checkFunctionAttributes(exp, sc, exp.f, fIsAliasParam);
checkAccess(exp.loc, sc, ue.e1, exp.f);
if (!exp.f.needThis())
{
Expand Down Expand Up @@ -4835,7 +4848,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
if (!exp.f || exp.f.errors)
return setError();

checkFunctionAttributes(exp, sc, exp.f);
checkFunctionAttributes(exp, sc, exp.f, fIsAliasParam);
checkAccess(exp.loc, sc, null, exp.f);

exp.e1 = new DotVarExp(exp.e1.loc, exp.e1, exp.f, false);
Expand Down Expand Up @@ -5082,7 +5095,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
}
}

checkFunctionAttributes(exp, sc, exp.f);
checkFunctionAttributes(exp, sc, exp.f, fIsAliasParam);
checkAccess(exp.loc, sc, null, exp.f);
if (exp.f.checkNestedReference(sc, exp.loc))
return setError();
Expand Down Expand Up @@ -13097,7 +13110,7 @@ private bool checkAddressCall(Scope* sc, CallExp ce, const(char)* action)
* f = function to be checked
* Returns: `true` if error occur.
*/
private bool checkFunctionAttributes(Expression exp, Scope* sc, FuncDeclaration f)
private bool checkFunctionAttributes(Expression exp, Scope* sc, FuncDeclaration f, bool fIsAliasParam = false)
{
with(exp)
{
Expand All @@ -13106,6 +13119,7 @@ private bool checkFunctionAttributes(Expression exp, Scope* sc, FuncDeclaration
error |= checkPurity(sc, f);
error |= checkSafety(sc, f);
error |= checkNogc(sc, f);
error |= checkCtonly(f, exp.loc, sc, fIsAliasParam);
return error;
}
}
Expand Down
1 change: 1 addition & 0 deletions dmd/hdrgen.d
Original file line number Diff line number Diff line change
Expand Up @@ -2937,6 +2937,7 @@ string stcToString(ref StorageClass stc)
SCstring(STC.disable, "@disable"),
SCstring(STC.future, "@__future"),
SCstring(STC.local, "__local"),
SCstring(STC.ctonly, "@__ctfe"),
];
foreach (ref entry; table)
{
Expand Down
8 changes: 8 additions & 0 deletions dmd/mtype.d
Original file line number Diff line number Diff line change
Expand Up @@ -4240,6 +4240,8 @@ extern (C++) final class TypeFunction : TypeNext
bool isInOutQual; /// inout on the qualifier
bool isctor; /// the function is a constructor
bool isreturnscope; /// `this` is returned by value
bool isCtonly; /// is compile time only (@__ctfe)
bool isCtonlyInferred; /// was compile time only inferred
}

import dmd.common.bitfields : generateBitFields;
Expand All @@ -4250,6 +4252,7 @@ extern (C++) final class TypeFunction : TypeNext
PURE purity = PURE.impure;
byte inuse;
Expressions* fargs; // function arguments
FuncDeclaration ctOnlyInferReason = null;

extern (D) this(ParameterList pl, Type treturn, LINK linkage, StorageClass stc = 0)
{
Expand All @@ -4270,6 +4273,8 @@ extern (C++) final class TypeFunction : TypeNext
this.isproperty = true;
if (stc & STC.live)
this.islive = true;
if (stc & STC.ctonly)
this.isCtonly = true;

if (stc & STC.ref_)
this.isref = true;
Expand Down Expand Up @@ -4324,6 +4329,7 @@ extern (C++) final class TypeFunction : TypeNext
t.trust = trust;
t.fargs = fargs;
t.isctor = isctor;
t.isCtonly = isCtonly;
return t;
}

Expand Down Expand Up @@ -7189,6 +7195,8 @@ void attributesApply(const TypeFunction tf, void delegate(string) dg, TRUSTforma
dg("scope");
if (tf.islive)
dg("@live");
if (tf.isCtonly)
dg("@__ctfe");

TRUST trustAttrib = tf.trust;

Expand Down
2 changes: 2 additions & 0 deletions dmd/mtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,8 @@ class TypeFunction : public TypeNext
bool isInOutQual() const;
void isInOutQual(bool v);
bool iswild() const;
bool isCtonly() const;
void isCtonly(bool v);

void accept(Visitor *v) { v->visit(this); }
};
Expand Down
1 change: 1 addition & 0 deletions dmd/parse.d
Original file line number Diff line number Diff line change
Expand Up @@ -9318,6 +9318,7 @@ LagainStc:
(ident == Id.live) ? STC.live :
(ident == Id.future) ? STC.future :
(ident == Id.disable) ? STC.disable :
(ident == Id.ctfe) ? STC.ctonly :
0;
}

Expand Down
3 changes: 3 additions & 0 deletions dmd/typesem.d
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,9 @@ extern(C++) Type typeSemantic(Type type, const ref Loc loc, Scope* sc)
if (sc.stc & STC.scopeinferred)
tf.isscopeinferred = true;

if (sc.stc & STC.ctonly)
tf.isCtonly = true;

// if (tf.isreturn && !tf.isref)
// tf.isScopeQual = true; // return by itself means 'return scope'

Expand Down
5 changes: 5 additions & 0 deletions gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ class CodegenVisitor : public Visitor {
//////////////////////////////////////////////////////////////////////////

void visit(FuncDeclaration *decl) override {
auto type = decl->type;
// don't generate code marked or inferred with @__ctfe
if (type && type->toTypeFunction()->isCtonly()) {
return;
}
// don't touch function aliases, they don't contribute any new symbols
if (!decl->isFuncAliasDeclaration()) {
DtoDefineFunction(decl);
Expand Down
26 changes: 26 additions & 0 deletions tests/compilable/ctonly_basic.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %ldc -c %s

int f(int x, int y) @__ctfe {
return x + y;
}

int g(int x)(int y) @__ctfe {
return x + y;
}

alias gf = g!2;

/* enums are fine */
enum e = g!4(2);
/* globals initializers are fine */
int z = f(2, 5);

void main() {
/* enums are fine */
enum x = f(2, 4);
enum y = gf(4);
/* static asserts are fine */
static assert (f(2, 2) == g!4(0));
/* array indeces are fine */
int[g!10(0)] arr;
}
29 changes: 29 additions & 0 deletions tests/compilable/ctonly_class.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %ldc -c %s

class C {
int v;
this(int v_) { v = v_; }
int f(int x) const @__ctfe { return x + v; }
static int g(int x) @__ctfe { return x + 2; }
}

class CtOnly {
int v;
this(int v_) @__ctfe { v = v_; }
int f(int x) const { return x + v; }
static int g(int x) { return x + 2; }
}

// can do this
enum v = new C(1).f(0);
// or this
static const c = new C(2);
enum v2 = c.f(2);

void main() {
C i = new C(2);
// or this
enum v = C.g(2);
// or this
enum e = new CtOnly(5).f(5);
}
52 changes: 52 additions & 0 deletions tests/compilable/ctonly_simple_map.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %ldc -c %s

import std.algorithm.searching;
import std.range;
import std.traits;

/// Checks is a function is @ctonly
enum isCtOnly(alias f) = canFind(only(__traits(getFunctionAttributes, f)), "@__ctfe");

/// Simplified @ctonly-aware version of `map`
auto simple_map(alias fun, Range)(Range r) {
return SimpleMapResult!(fun, Range)(r);
}

private struct SimpleMapResult(alias fun, Range) {
alias R = Unqual!Range;
enum funIsCtOnly = isCtOnly!fun;
R _input;
this(R input) {
_input = input;
}
void popFront() {
_input.popFront();
}
@property bool empty() {
return _input.empty;
}
static if (funIsCtOnly) {
/// If `fun` is @ctonly, `front` must be @ctonly too
@property auto ref front() @__ctfe {
return fun(_input.front);
}
} else {
@property auto ref front() {
return fun(_input.front);
}
}
}

int f(int x) @__ctfe {
return x + 1;
}

import std.algorithm.iteration;
import std.array;

void main() {
enum f2 = f(2);
// enum a = map!f([1, 2, 3]).array; // doesn't work
enum a2 = simple_map!f([1, 2, 3]); // that works
enum a2f = a2.front; // that works too, even though `a2.array` won't work, since `array` must be @ctonly-aware as well
}
Loading
Loading