diff --git a/gen/classes.cpp b/gen/classes.cpp index 9938e3f40b..f7791bfb18 100644 --- a/gen/classes.cpp +++ b/gen/classes.cpp @@ -277,6 +277,12 @@ DValue *DtoCastClass(Loc loc, DValue *val, Type *_to) { Type *from = val->type->toBasetype(); TypeClass *fc = static_cast(from); + // Qualifier-only casts are semantic no-ops; avoid dynamic cast routing. + if (dmd::equivalent(from, to)) { + Logger::println("qualifier-only cast"); + return new DImValue(_to, DtoRVal(val)); + } + // copy DMD logic: // if to isBaseOf from with offset: (to ? to + offset : null) // else if from is C++ and to is C++: to @@ -335,7 +341,7 @@ bool DtoIsObjcLinkage(Type *_to) { DtoResolveClass(to->sym); return to->sym->classKind == ClassKind::objc; } - + return false; } @@ -391,7 +397,7 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) { // In this case we want to call the Objective-C runtime to first // get a Class object from the `id`. // Then check if class_conformsToProtocol returns true, - // if it does, then we can cast and return the casted value, + // if it does, then we can cast and return the casted value, // otherwise return null. if (DtoIsObjcLinkage(_to)) { llvm::Function *getClassFunc = @@ -400,10 +406,10 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) { llvm::Function *kindOfProtocolFunc = getRuntimeFunction(loc, gIR->module, "class_conformsToProtocol"); - // id -> Class + // id -> Class LLValue *obj = DtoRVal(val); LLValue *objClass = gIR->CreateCallOrInvoke(getClassFunc, obj); - + // Get prototype_t handle LLValue *protoTy = getNullPtr(); if (auto ifhndl = _to->isClassHandle()->isInterfaceDeclaration()) { @@ -413,7 +419,7 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) { // Class && kindOfProtocolFunc(Class) ? id : null LLValue *ret = gIR->ir->CreateSelect( gIR->CreateCallOrInvoke(kindOfProtocolFunc, objClass, protoTy), - obj, + obj, getNullPtr() ); return new DImValue(_to, ret); diff --git a/gen/tocall.cpp b/gen/tocall.cpp index c1b0bc1b3c..7470ecb820 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -727,14 +727,15 @@ class ImplicitArgumentsBuilder { // class pointer Type *thistype = gIR->func()->decl->vthis->type; if (thistype != iface->type) { - DImValue *dthis = new DImValue(thistype, DtoLoad(DtoType(thistype),thisptrLval)); - thisptrLval = DtoAllocaDump(DtoCastClass(loc, dthis, iface->type)); + auto thisVal = DtoLoad(DtoType(thistype), thisptrLval); + DImValue dthis(thistype, thisVal); + thisptrLval = DtoAllocaDump(DtoCastClass(loc, &dthis, iface->type)); } } } args.push_back(thisptrLval); } else if (thiscall && dfnval && dfnval->vthis) { - + if (objccall && directcall) { // ... or a Objective-c direct call argument diff --git a/tests/codegen/gh5114.d b/tests/codegen/gh5114.d new file mode 100644 index 0000000000..6d5a9188dd --- /dev/null +++ b/tests/codegen/gh5114.d @@ -0,0 +1,79 @@ +module tests.codegen.gh5114; + +// During interface contract context setup, a qualifier-only cast for the same +// interface symbol (e.g., `const(I)` -> `I`) must be handled as a repaint/ +// bitcast and must not route through dynamic interface cast lowering. +// +// True dynamic interface casts (different interface symbols) are still valid +// and are covered below. +// +// RUN: %ldc -c %s +// RUN: %ldc -unittest -main -run %s + +extern (D): + +interface I { + void fn() const + out { + // Positive case: contract body performs a true interface -> interface cast. + auto b = cast(const(B)) this; + assert(b !is null); + assert(b.b() == 22); + }; +} + +interface A { + int a(); +} + +interface B { + int b() const; +} + +interface J : I { +} + +class C : I, A, B { + override void fn() const + out (; true) + { + } + + override int a() { + return 11; + } + + override int b() const { + return 22; + } +} + +class D : J, B { + override void fn() const + out (; true) + { + } + + override int b() const { + return 22; + } +} + +unittest { + A a = new C(); + + // True dynamic cast: interface -> interface, resolved via druntime cast hook. + B b = cast(B) a; + + assert(b !is null); + assert(b.b() == 22); + + // Ensure the interface contract executes in extern(D) call flow. + I i = cast(I) a; + i.fn(); + + // Derived-interface call flow for I's contract; this may route through a + // non-same-interface contract context conversion. + J j = new D(); + j.fn(); +}