public boolean add(
		ATNConfig config,
		DoubleKeyMap<PredictionContext,PredictionContext,PredictionContext> mergeCache)
	{
		if ( readonly ) throw new IllegalStateException("This set is readonly");
		if ( config.semanticContext != SemanticContext.Empty.Instance ) {
			hasSemanticContext = true;
		}
		if (config.getOuterContextDepth() > 0) {
			dipsIntoOuterContext = true;
		}
		ATNConfig existing = configLookup.getOrAdd(config);
		if ( existing==config ) { // we added this new one
			cachedHashCode = -1;
			configs.add(config);  // track order here
			return true;
		}
		// a previous (s,i,pi,_), merge with it and save result
		boolean rootIsWildcard = !fullCtx;
		PredictionContext merged =
			PredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache);
		// no need to check for existing.context, config.context in cache
		// since only way to create new graphs is "call rule" and here. We
		// cache at both places.
		existing.reachesIntoOuterContext =
			Math.max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext);

		// make sure to preserve the precedence filter suppression during the merge
		if (config.isPrecedenceFilterSuppressed()) {
			existing.setPrecedenceFilterSuppressed(true);
		}

		existing.context = merged; // replace context; no need to alt mapping
		return true;
	}
Adding a new config means merging contexts with existing configs for
{@code (s, i, pi, _)}, where {@code s} is the
{@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
{@code pi} is the {@link ATNConfig#semanticContext}. We use
{@code (s,i,pi)} as key.

<p>This method updates {@link #dipsIntoOuterContext} and
{@link #hasSemanticContext} when necessary.</p>
runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java