So if I have understood you right, there are some options. I would prefer to use a common interface, and as contract B1 doesn't implement the interface, you should mark it as abstract:
interface IContractB { function someAbstract() external view returns (uint256); } abstract contract B1 is IContractB { function foo () view external returns (uint256) { return this.someAbstract(); } } contract B2 is IContractB { function someAbstract() override external view returns (uint256) { return 5; } } contract A is B1, B2 { }
Now, when you deploy A and call the method foo, it will run the method foo from B1, which calls method someAbstract, which is implemented in B2. The return will be 5.
There is even a way without the common interface, but it would be the antipattern and additionally, it requires the shallow methods in contract A
abstract contract B1 { function foo () view external returns (uint256) { return this.someAbstract(); } function someAbstract() public view virtual returns (uint256); } contract B2 { function someAbstract() public view virtual returns (uint256) { return 5; } } contract A is B1, B2 { function someAbstract() override(B1, B2) public view virtual returns (uint256) { return super.someAbstract(); } }