How to use data structures referencing Context in macros?

I’m new to macros and most of what I find on Internet are small tutorial macros, where this problem doesn’t exist.
All types are bound to a particular Context, therefore I need to pass the Context object around.
But I’d like to use not just methods, but also data structures (classes, objects). Here is a silly, minimized example:

    class CommonTypes(val c: whitebox.Context) {
      import c.universe._ 
      val intType: c.Type = c.weakTypeOf[Int]
    }

    /// inside of a macro impl:
    import c.universe._
    val ct = new CommonTypes(c)
    val intType: c.Type = ct.intType  // <----------- type mismatch, ct.c.Type doesn't match c.Type

Then I run into trouble, because after passing the Context as an argument to such class, the compiler somehow forgets about the fact that intType is based on the same Context instance that was passed to CommonTypes and complains with a type mismatch.
It looks like any data structure I want to create, immediately gets incompatible with any structures inside of the Context.

Is there any pattern around this?
How to structure bigger macros without passing around a ton of objects?
How do I share some data structures I create across multiple macro invocations, to save on compiler time?

There are two ways to do this:

Base trait for macro bundle

trait MacroUtils {
  val c: whitebox.Context
  import c.universe._
  // your common macro utils go here
}

class ActualMacroImpls(val c: whitebox.Context) extends MacroUtils {
  import c.universe._
  // your macro impls go here
}

Separate, dependently typed class

class MacroUtils[C <: whitebox.Context with Singleton](val c: C) {
  import c.universe._
  // your macro utils go here
}

class ActualMacroImpls(val c: whitebox.Context) {
  import c.universe._
  val utils = new MacroUtils[c.type](c)
  import utils._
  // your macro impls go here
}

Thanks! This is very useful.

However, one thing is concerning me - with this approach, all object instances would be created per each macro call initiated from regular Scala code. But what if I use the same macro with the same arguments / type parameters in different places of code base? Assuming my macro code is idempotent, can I somehow avoid running all macro code twice and generating identical code twice? In other words, is there a way to cache and reuse macro results? Could the first macro execution memoize its parameters together with the result Tree and avoid most work on the second run? Or maybe it is already happening automatically?

I think such behaviour would be very useful for implicit macros. If a macro is used to generate an implicit object, I’d like to have only one copy of implicit for given parametrization of implicit, and not generate the implicit over and over again if it is needed in various contexts.