CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-softwaremill-macwire--macros

Zero-cost, compile-time, type-safe dependency injection library providing macros for automatic instance creation in Scala applications

Pending
Overview
Eval results
Files

context-dependent-wiring.mddocs/

Context-Dependent Wiring

Context-dependent wiring uses dependencies available in the surrounding context (trait, class, or object scope). This family of functions includes wire, wireRec, and wireSet, each providing different approaches to dependency resolution within the current scope.

Core Functions

wire

Creates an instance of type T using dependencies from the surrounding context. Dependencies are resolved from values available in the enclosing scope and inherited scopes.

def wire[T]: T

Parameters: None (uses type parameter to determine target type)

Returns: Instance of type T with dependencies resolved from context

Behavior:

  • Looks for values in enclosing trait/class/object
  • Searches inherited values from parent traits/classes
  • Uses public primary constructor or companion object apply method
  • Fails at compile time if dependencies cannot be resolved

wireRec

Creates an instance of type T with recursive dependency creation. Missing dependencies are automatically created using constructors or apply methods.

def wireRec[T]: T

Parameters: None (uses type parameter to determine target type)

Returns: Instance of type T with all dependencies created recursively

Behavior:

  • First attempts to find dependencies in surrounding context like wire
  • Automatically creates missing dependencies using constructors/apply methods
  • Recursively creates nested dependencies as needed
  • Excludes primitive types and standard library types from automatic creation

wireSet

Collects all instances of the given type available in the surrounding context.

def wireSet[T]: Set[T]

Parameters: None (uses type parameter to determine collection type)

Returns: Set[T] containing all available instances of type T from context

Behavior:

  • Searches the enclosing scope for all values of type T
  • Returns a Set containing all found instances
  • The specific Set implementation depends on imports in scope (can be mutable or immutable)

Usage Examples

Basic Context Wiring

import com.softwaremill.macwire._

class DatabaseAccess()
class SecurityFilter() 
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)

trait UserModule {
  // Define dependencies in context
  lazy val databaseAccess = new DatabaseAccess()
  lazy val securityFilter = new SecurityFilter()
  
  // Wire using available context dependencies
  lazy val userFinder = wire[UserFinder]
}

Module Composition with Inheritance

trait DatabaseModule {
  lazy val databaseAccess = new DatabaseAccess()
}

trait SecurityModule {
  lazy val securityFilter = new SecurityFilter()
}

trait UserModule extends DatabaseModule with SecurityModule {
  // Can wire using dependencies from parent traits
  lazy val userFinder = wire[UserFinder]
  lazy val userStatusReader = wire[UserStatusReader]
}

Module Composition with Imports

class DatabaseModule {
  lazy val databaseAccess = new DatabaseAccess()
}

class UserModule(databaseModule: DatabaseModule) {
  // Import makes dependencies available to wire
  import databaseModule._
  
  lazy val securityFilter = new SecurityFilter()
  lazy val userFinder = wire[UserFinder]
}

Recursive Wiring

class Service1()
class Service2(service1: Service1) 
class Service3(service1: Service1, service2: Service2)

trait AppModule {
  // wireRec creates missing dependencies automatically
  lazy val service3 = wireRec[Service3]
  // Equivalent to manually defining:
  // lazy val service1 = new Service1()
  // lazy val service2 = new Service2(service1)
  // lazy val service3 = new Service3(service1, service2)
}

Set Collection

trait Plugin
class DatabasePlugin extends Plugin
class CachePlugin extends Plugin  
class MetricsPlugin extends Plugin

trait PluginModule {
  lazy val databasePlugin = new DatabasePlugin()
  lazy val cachePlugin = new CachePlugin()
  lazy val metricsPlugin = new MetricsPlugin()
  
  // Collects all Plugin instances
  lazy val allPlugins: Set[Plugin] = wireSet[Plugin]
}

Factory Methods and Apply

class ConfiguredService private(config: Config)

object ConfiguredService {
  def apply(config: Config): ConfiguredService = new ConfiguredService(config)
}

trait ServiceModule {
  lazy val config = loadConfig()
  
  // Uses companion object apply method
  lazy val configuredService = wire[ConfiguredService]
}

Scopes and Lifecycle

trait WebModule {
  // Singleton scope - same instance reused
  lazy val singletonService = wire[SingletonService]
  
  // Prototype scope - new instance each time
  def prototypeService = wire[PrototypeService]
}

Advanced Patterns

Conditional Wiring

trait ConditionalModule {
  def isDevelopment: Boolean
  
  lazy val logger = if (isDevelopment) wire[ConsoleLogger] else wire[FileLogger]
}

Generic Type Wiring

class Repository[T](dao: DAO[T])
class DAO[T]()

trait RepositoryModule {
  lazy val userDao = new DAO[User]()
  lazy val userRepository = wire[Repository[User]]  // Generic types preserved
}

Multiple Implementations

trait EmailService
class SmtpEmailService extends EmailService
class SendGridEmailService extends EmailService

trait ServiceModule {
  // Specify which implementation to wire
  lazy val emailService: EmailService = wire[SmtpEmailService]
}

Scope and Context Rules

Dependency Lookup Order

  1. Current scope values: val, lazy val, def in current trait/class/object
  2. Inherited values: Values from parent traits and classes
  3. Imported values: Values made available through import statements

Supported Member Types

  • val and lazy val: Create singleton dependencies
  • def: Create new instances on each access
  • Inherited members from parent traits/classes
  • Imported members from other modules

Type Resolution

  • Exact type matching required
  • Subtype relationships not automatically resolved
  • Generic type parameters must match exactly
  • Abstract types resolved using available concrete implementations

Error Handling

Compile-Time Errors

  • Missing Dependencies: "Cannot find a value of type [T]"
  • Ambiguous Dependencies: "Found multiple values of type [T]"
  • Unsupported Constructor: "Cannot find a public constructor"
  • Recursive Dependencies: "Cyclic dependencies detected"

Debugging Tips

  • Use explicit type annotations to clarify ambiguous cases
  • Check that all required dependencies are available in scope
  • Verify that constructors are public and accessible
  • Use companion object apply methods for complex initialization logic

Install with Tessl CLI

npx tessl i tessl/maven-com-softwaremill-macwire--macros

docs

context-dependent-wiring.md

context-free-wiring.md

dynamic-instance-access.md

factory-based-wiring.md

index.md

tile.json