blob: 1c2f22653282b7d343c8b3383309ea99a3487a52 [file] [log] [blame]
/**
* Struct and union declarations.
*
* Specification: $(LINK2 https://dlang.org/spec/struct.html, Structs, Unions)
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dstruct.d, _dstruct.d)
* Documentation: https://dlang.org/phobos/dmd_dstruct.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dstruct.d
*/
module dmd.dstruct;
import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.declaration;
import dmd.dmodule;
import dmd.dscope;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.mtype;
import dmd.opover;
import dmd.target;
import dmd.tokens;
import dmd.typesem;
import dmd.typinf;
import dmd.visitor;
/***************************************
* Search sd for a member function of the form:
* `extern (D) string toString();`
* Params:
* sd = struct declaration to search
* Returns:
* FuncDeclaration of `toString()` if found, `null` if not
*/
extern (C++) FuncDeclaration search_toString(StructDeclaration sd)
{
Dsymbol s = search_function(sd, Id.tostring);
FuncDeclaration fd = s ? s.isFuncDeclaration() : null;
if (fd)
{
__gshared TypeFunction tftostring;
if (!tftostring)
{
tftostring = new TypeFunction(ParameterList(), Type.tstring, LINK.d);
tftostring = tftostring.merge().toTypeFunction();
}
fd = fd.overloadExactMatch(tftostring);
}
return fd;
}
/***************************************
* Request additional semantic analysis for TypeInfo generation.
* Params:
* sc = context
* t = type that TypeInfo is being generated for
*/
extern (C++) void semanticTypeInfo(Scope* sc, Type t)
{
if (sc)
{
if (sc.intypeof)
return;
if (sc.flags & (SCOPE.ctfe | SCOPE.compile))
return;
}
if (!t)
return;
void visitVector(TypeVector t)
{
semanticTypeInfo(sc, t.basetype);
}
void visitAArray(TypeAArray t)
{
semanticTypeInfo(sc, t.index);
semanticTypeInfo(sc, t.next);
}
void visitStruct(TypeStruct t)
{
//printf("semanticTypeInfo.visit(TypeStruct = %s)\n", t.toChars());
StructDeclaration sd = t.sym;
/* Step 1: create TypeInfoDeclaration
*/
if (!sc) // inline may request TypeInfo.
{
Scope scx;
scx._module = sd.getModule();
getTypeInfoType(sd.loc, t, &scx);
sd.requestTypeInfo = true;
}
else if (!sc.minst)
{
// don't yet have to generate TypeInfo instance if
// the typeid(T) expression exists in speculative scope.
}
else
{
getTypeInfoType(sd.loc, t, sc);
sd.requestTypeInfo = true;
// https://issues.dlang.org/show_bug.cgi?id=15149
// if the typeid operand type comes from a
// result of auto function, it may be yet speculative.
// unSpeculative(sc, sd);
}
/* Step 2: If the TypeInfo generation requires sd.semantic3, run it later.
* This should be done even if typeid(T) exists in speculative scope.
* Because it may appear later in non-speculative scope.
*/
if (!sd.members)
return; // opaque struct
if (!sd.xeq && !sd.xcmp && !sd.postblit && !sd.tidtor && !sd.xhash && !search_toString(sd))
return; // none of TypeInfo-specific members
// If the struct is in a non-root module, run semantic3 to get
// correct symbols for the member function.
if (sd.semanticRun >= PASS.semantic3)
{
// semantic3 is already done
}
else if (TemplateInstance ti = sd.isInstantiated())
{
if (ti.minst && !ti.minst.isRoot())
Module.addDeferredSemantic3(sd);
}
else
{
if (sd.inNonRoot())
{
//printf("deferred sem3 for TypeInfo - sd = %s, inNonRoot = %d\n", sd.toChars(), sd.inNonRoot());
Module.addDeferredSemantic3(sd);
}
}
}
void visitTuple(TypeTuple t)
{
if (t.arguments)
{
foreach (arg; *t.arguments)
{
semanticTypeInfo(sc, arg.type);
}
}
}
/* Note structural similarity of this Type walker to that in isSpeculativeType()
*/
Type tb = t.toBasetype();
switch (tb.ty)
{
case Tvector: visitVector(tb.isTypeVector()); break;
case Taarray: visitAArray(tb.isTypeAArray()); break;
case Tstruct: visitStruct(tb.isTypeStruct()); break;
case Ttuple: visitTuple (tb.isTypeTuple()); break;
case Tclass:
case Tenum: break;
default: semanticTypeInfo(sc, tb.nextOf()); break;
}
}
enum StructFlags : int
{
none = 0x0,
hasPointers = 0x1, // NB: should use noPointers as in ClassFlags
}
/***********************************************************
* All `struct` declarations are an instance of this.
*/
extern (C++) class StructDeclaration : AggregateDeclaration
{
FuncDeclarations postblits; // Array of postblit functions
FuncDeclaration postblit; // aggregate postblit
FuncDeclaration xeq; // TypeInfo_Struct.xopEquals
FuncDeclaration xcmp; // TypeInfo_Struct.xopCmp
FuncDeclaration xhash; // TypeInfo_Struct.xtoHash
extern (C++) __gshared FuncDeclaration xerreq; // object.xopEquals
extern (C++) __gshared FuncDeclaration xerrcmp; // object.xopCmp
// ABI-specific type(s) if the struct can be passed in registers
TypeTuple argTypes;
structalign_t alignment; // alignment applied outside of the struct
ThreeState ispod; // if struct is POD
// `bool` fields that are compacted into bit fields in a string mixin
private extern (D) static struct BitFields
{
bool zeroInit; // !=0 if initialize with 0 fill
bool hasIdentityAssign; // true if has identity opAssign
bool hasBlitAssign; // true if opAssign is a blit
bool hasIdentityEquals; // true if has identity opEquals
bool hasNoFields; // has no fields
bool hasCopyCtor; // copy constructor
bool hasPointerField; // members with indirections
bool hasVoidInitPointers; // void-initialized unsafe fields
bool hasSystemFields; // @system members
bool hasFieldWithInvariant; // invariants
bool computedTypeProperties;// the above 3 fields are computed
// Even if struct is defined as non-root symbol, some built-in operations
// (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo.
// For those, today TypeInfo_Struct is generated in COMDAT.
bool requestTypeInfo;
}
import dmd.common.bitfields : generateBitFields;
mixin(generateBitFields!(BitFields, ushort));
extern (D) this(const ref Loc loc, Identifier id, bool inObject)
{
super(loc, id);
zeroInit = false; // assume false until we do semantic processing
ispod = ThreeState.none;
// For forward references
type = new TypeStruct(this);
if (inObject)
{
if (id == Id.ModuleInfo && !Module.moduleinfo)
Module.moduleinfo = this;
}
}
static StructDeclaration create(const ref Loc loc, Identifier id, bool inObject)
{
return new StructDeclaration(loc, id, inObject);
}
override StructDeclaration syntaxCopy(Dsymbol s)
{
StructDeclaration sd =
s ? cast(StructDeclaration)s
: new StructDeclaration(loc, ident, false);
ScopeDsymbol.syntaxCopy(sd);
return sd;
}
override final Dsymbol search(const ref Loc loc, Identifier ident, int flags = SearchLocalsOnly)
{
//printf("%s.StructDeclaration::search('%s', flags = x%x)\n", toChars(), ident.toChars(), flags);
if (_scope && !symtab)
dsymbolSemantic(this, _scope);
if (!members || !symtab) // opaque or semantic() is not yet called
{
// .stringof is always defined (but may be hidden by some other symbol)
if(ident != Id.stringof && !(flags & IgnoreErrors) && semanticRun < PASS.semanticdone)
error("is forward referenced when looking for `%s`", ident.toChars());
return null;
}
return ScopeDsymbol.search(loc, ident, flags);
}
override const(char)* kind() const
{
return "struct";
}
override final void finalizeSize()
{
//printf("StructDeclaration::finalizeSize() %s, sizeok = %d\n", toChars(), sizeok);
assert(sizeok != Sizeok.done);
if (sizeok == Sizeok.inProcess)
{
return;
}
sizeok = Sizeok.inProcess;
//printf("+StructDeclaration::finalizeSize() %s, fields.dim = %d, sizeok = %d\n", toChars(), fields.dim, sizeok);
fields.setDim(0); // workaround
// Set the offsets of the fields and determine the size of the struct
FieldState fieldState;
bool isunion = isUnionDeclaration() !is null;
for (size_t i = 0; i < members.dim; i++)
{
Dsymbol s = (*members)[i];
s.setFieldOffset(this, fieldState, isunion);
}
if (type.ty == Terror)
{
errors = true;
return;
}
if (structsize == 0)
{
hasNoFields = true;
alignsize = 1;
// A fine mess of what size a zero sized struct should be
final switch (classKind)
{
case ClassKind.d:
case ClassKind.cpp:
structsize = 1;
break;
case ClassKind.c:
case ClassKind.objc:
if (target.c.bitFieldStyle == TargetC.BitFieldStyle.MS)
{
/* Undocumented MS behavior for:
* struct S { int :0; };
*/
structsize = 4;
}
else if (target.c.bitFieldStyle == TargetC.BitFieldStyle.DM)
{
structsize = 0;
alignsize = 0;
}
else
structsize = 0;
break;
}
}
// Round struct size up to next alignsize boundary.
// This will ensure that arrays of structs will get their internals
// aligned properly.
if (alignment.isDefault() || alignment.isPack())
structsize = (structsize + alignsize - 1) & ~(alignsize - 1);
else
structsize = (structsize + alignment.get() - 1) & ~(alignment.get() - 1);
sizeok = Sizeok.done;
//printf("-StructDeclaration::finalizeSize() %s, fields.dim = %d, structsize = %d\n", toChars(), fields.dim, structsize);
if (errors)
return;
// Calculate fields[i].overlapped
if (checkOverlappedFields())
{
errors = true;
return;
}
// Determine if struct is all zeros or not
zeroInit = true;
foreach (vd; fields)
{
if (vd._init)
{
if (vd._init.isVoidInitializer())
/* Treat as 0 for the purposes of putting the initializer
* in the BSS segment, or doing a mass set to 0
*/
continue;
// Zero size fields are zero initialized
if (vd.type.size(vd.loc) == 0)
continue;
// Examine init to see if it is all 0s.
auto exp = vd.getConstInitializer();
if (!exp || !_isZeroInit(exp))
{
zeroInit = false;
break;
}
}
else if (!vd.type.isZeroInit(loc))
{
zeroInit = false;
break;
}
}
argTypes = target.toArgTypes(type);
}
/// Compute cached type properties for `TypeStruct`
extern(D) final void determineTypeProperties()
{
if (computedTypeProperties)
return;
foreach (vd; fields)
{
if (vd.storage_class & STC.ref_ || vd.hasPointers())
hasPointerField = true;
if (vd._init && vd._init.isVoidInitializer() && vd.type.hasPointers())
hasVoidInitPointers = true;
if (vd.storage_class & STC.system || vd.type.hasSystemFields())
hasSystemFields = true;
if (!vd._init && vd.type.hasVoidInitPointers())
hasVoidInitPointers = true;
if (vd.type.hasInvariant())
hasFieldWithInvariant = true;
}
computedTypeProperties = true;
}
/***************************************
* Determine if struct is POD (Plain Old Data).
*
* POD is defined as:
* $(OL
* $(LI not nested)
* $(LI no postblits, destructors, or assignment operators)
* $(LI no `ref` fields or fields that are themselves non-POD)
* )
* The idea being these are compatible with C structs.
*
* Returns:
* true if struct is POD
*/
final bool isPOD()
{
// If we've already determined whether this struct is POD.
if (ispod != ThreeState.none)
return (ispod == ThreeState.yes);
ispod = ThreeState.yes;
if (enclosing || postblit || dtor || hasCopyCtor)
{
ispod = ThreeState.no;
return false;
}
// Recursively check all fields are POD.
for (size_t i = 0; i < fields.dim; i++)
{
VarDeclaration v = fields[i];
if (v.storage_class & STC.ref_)
{
ispod = ThreeState.no;
return false;
}
Type tv = v.type.baseElemOf();
if (tv.ty == Tstruct)
{
TypeStruct ts = cast(TypeStruct)tv;
StructDeclaration sd = ts.sym;
if (!sd.isPOD())
{
ispod = ThreeState.no;
return false;
}
}
}
return (ispod == ThreeState.yes);
}
override final inout(StructDeclaration) isStructDeclaration() inout @nogc nothrow pure @safe
{
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
final uint numArgTypes() const
{
return argTypes && argTypes.arguments ? cast(uint) argTypes.arguments.dim : 0;
}
final Type argType(uint index)
{
return index < numArgTypes() ? (*argTypes.arguments)[index].type : null;
}
/***************************************
* Verifies whether the struct declaration has a
* constructor that is not a copy constructor.
* Optionally, it can check whether the struct
* declaration has a regular constructor, that
* is not disabled.
*
* Params:
* checkDisabled = if the struct has a regular
non-disabled constructor
* Returns:
* true, if the struct has a regular (optionally,
* not disabled) constructor, false otherwise.
*/
final bool hasRegularCtor(bool checkDisabled = false)
{
if (!ctor)
return false;
bool result;
overloadApply(ctor, (Dsymbol s)
{
if (auto td = s.isTemplateDeclaration())
{
if (checkDisabled && td.onemember)
{
if (auto ctorDecl = td.onemember.isCtorDeclaration())
{
if (ctorDecl.storage_class & STC.disable)
return 0;
}
}
result = true;
return 1;
}
if (auto ctorDecl = s.isCtorDeclaration())
{
if (!ctorDecl.isCpCtor && (!checkDisabled || !(ctorDecl.storage_class & STC.disable)))
{
result = true;
return 1;
}
}
return 0;
});
return result;
}
}
/**********************************
* Determine if exp is all binary zeros.
* Params:
* exp = expression to check
* Returns:
* true if it's all binary 0
*/
private bool _isZeroInit(Expression exp)
{
switch (exp.op)
{
case EXP.int64:
return exp.toInteger() == 0;
case EXP.null_:
case EXP.false_:
return true;
case EXP.structLiteral:
{
auto sle = cast(StructLiteralExp) exp;
foreach (i; 0 .. sle.sd.fields.dim)
{
auto field = sle.sd.fields[i];
if (field.type.size(field.loc))
{
auto e = (*sle.elements)[i];
if (e ? !_isZeroInit(e)
: !field.type.isZeroInit(field.loc))
return false;
}
}
return true;
}
case EXP.arrayLiteral:
{
auto ale = cast(ArrayLiteralExp)exp;
const dim = ale.elements ? ale.elements.dim : 0;
if (ale.type.toBasetype().ty == Tarray) // if initializing a dynamic array
return dim == 0;
foreach (i; 0 .. dim)
{
if (!_isZeroInit(ale[i]))
return false;
}
/* Note that true is returned for all T[0]
*/
return true;
}
case EXP.string_:
{
StringExp se = cast(StringExp)exp;
if (se.type.toBasetype().ty == Tarray) // if initializing a dynamic array
return se.len == 0;
foreach (i; 0 .. se.len)
{
if (se.getCodeUnit(i))
return false;
}
return true;
}
case EXP.vector:
{
auto ve = cast(VectorExp) exp;
return _isZeroInit(ve.e1);
}
case EXP.float64:
case EXP.complex80:
{
import dmd.root.ctfloat : CTFloat;
return (exp.toReal() is CTFloat.zero) &&
(exp.toImaginary() is CTFloat.zero);
}
default:
return false;
}
}
/***********************************************************
* Unions are a variation on structs.
*/
extern (C++) final class UnionDeclaration : StructDeclaration
{
extern (D) this(const ref Loc loc, Identifier id)
{
super(loc, id, false);
}
override UnionDeclaration syntaxCopy(Dsymbol s)
{
assert(!s);
auto ud = new UnionDeclaration(loc, ident);
StructDeclaration.syntaxCopy(ud);
return ud;
}
override const(char)* kind() const
{
return "union";
}
override inout(UnionDeclaration) isUnionDeclaration() inout
{
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
}